From 80df3ee8fce78500e98adf0f51b0e3f8b3fb99f9 Mon Sep 17 00:00:00 2001 From: Kory Draughn Date: Tue, 3 Sep 2024 18:03:21 -0400 Subject: [PATCH] [7229] Refactor startup/shutdown logic. --- CMakeLists.txt | 5 + lib/api/CMakeLists.txt | 4 + lib/api/include/irods/apiHeaderAll.h | 3 + lib/api/include/irods/apiNumberData.h | 2 + lib/api/include/irods/apiTable.hpp | 18 + lib/api/include/irods/api_pack_table.hpp | 2 + lib/api/include/irods/delay_rule_tag.h | 43 + lib/api/include/irods/delay_rule_tag_clear.h | 42 + lib/api/include/irods/get_active_agents.h | 44 + lib/api/src/rc_delay_rule_tag.cpp | 14 + lib/api/src/rc_delay_rule_tag_clear.cpp | 14 + lib/core/include/irods/dns_cache.hpp | 14 + lib/core/include/irods/hostname_cache.hpp | 14 + .../irods/irods_client_server_negotiation.hpp | 6 +- .../include/irods/irods_default_paths.hpp.in | 18 +- lib/core/include/irods/irods_load_plugin.hpp | 51 +- lib/core/include/irods/irods_logger.hpp | 3 +- .../include/irods/irods_server_properties.hpp | 13 +- lib/core/include/irods/irods_version.h.in | 12 +- lib/core/include/irods/rcMisc.h | 4 + lib/core/include/irods/rodsErrorTable.h | 3 + lib/core/src/dns_cache.cpp | 18 + lib/core/src/hostname_cache.cpp | 18 + lib/core/src/irods_client_negotiation.cpp | 46 +- lib/core/src/irods_default_paths.cpp | 32 +- .../irods_get_full_path_for_config_file.cpp | 27 +- lib/core/src/irods_logger.cpp | 40 +- lib/core/src/irods_server_properties.cpp | 51 +- lib/core/src/rcMisc.cpp | 28 + lib/core/src/sockComm.cpp | 16 +- packaging/postinstall.sh | 1 + packaging/server_config.json.template | 31 +- plugins/database/src/db_plugin.cpp | 91 + plugins/network/src/ssl.cpp | 63 +- plugins/network/src/tcp.cpp | 76 +- .../irods_rule_language/src/configuration.cpp | 3 +- .../src/irods_rule_language.cpp | 26 +- .../rule_engines/src/cpp_default_policy.cpp | 8 + plugins/rule_engines/src/passthrough.cpp | 60 +- schemas/README.md | 12 - schemas/configuration/v4/grid_status.json.in | 14 - .../{v4 => v5}/client_environment.json.in | 2 +- .../{v4 => v5}/client_hints.json.in | 2 +- .../configuration_directory.json.in | 2 +- .../{v4 => v5}/database_config.json.in | 2 +- .../{v4 => v5}/host_access_control.json.in | 2 +- .../{v4 => v5}/host_resolution.json.in | 2 +- .../configuration/{v4 => v5}/plugin.json.in | 2 +- .../configuration/{v4 => v5}/resource.json.in | 2 +- .../{v4 => v5}/rule_engine.json.in | 2 +- .../configuration/{v4 => v5}/server.json.in | 2 +- .../{v4 => v5}/server_config.json.in | 16 +- .../{v4 => v5}/server_status.json.in | 2 +- .../service_account_environment.json.in | 2 +- .../unattended_installation.json.in | 2 +- .../configuration/{v4 => v5}/version.json.in | 2 +- .../{v4 => v5}/zone_bundle.json.in | 2 +- .../v1/server_control_plane_command.json | 8 - scripts/core_tests_list.json | 1 - scripts/irods/configuration.py | 92 +- scripts/irods/controller.py | 285 +- scripts/irods/database_interface.py | 2 +- scripts/irods/database_upgrade.py | 11 + scripts/irods/json_validation.py | 113 - scripts/irods/lib.py | 14 - scripts/irods/paths.py | 17 +- scripts/irods/test/test_control_plane.py | 270 - scripts/irods/upgrade_configuration.py | 21 + scripts/irods_control.py | 5 +- scripts/pid_age.py | 14 - scripts/setup_irods.py | 2 + scripts/system_identification.py | 39 - scripts/validate_json.py | 31 - server/CMakeLists.txt | 52 +- server/api/CMakeLists.txt | 4 + .../api/include/irods/rs_delay_rule_tag.hpp | 15 + .../include/irods/rs_delay_rule_tag_clear.hpp | 15 + server/api/src/rsExecCmd.cpp | 41 +- server/api/src/rsProcStat.cpp | 68 +- server/api/src/rsServerReport.cpp | 40 +- server/api/src/rs_delay_rule_tag.cpp | 73 + server/api/src/rs_delay_rule_tag_clear.cpp | 68 + server/control_plane/CMakeLists.txt | 53 - .../irods/irods_server_control_plane.hpp | 155 - server/core/CMakeLists.txt | 4 - server/core/include/irods/agent_globals.hpp | 26 + server/core/include/irods/initServer.hpp | 2 +- .../irods/irods_api_calling_functions.hpp | 14 + .../irods/irods_database_constants.hpp | 2 + server/core/include/irods/irods_signal.hpp | 16 +- server/core/include/irods/miscServerFunct.hpp | 2 + server/core/include/irods/procLog.h | 17 - server/core/include/irods/rodsAgent.hpp | 26 - server/core/src/initServer.cpp | 46 +- .../core/src/irods_api_calling_functions.cpp | 10 + server/core/src/irods_resource_manager.cpp | 2 +- server/core/src/irods_server_negotiation.cpp | 32 +- server/core/src/irods_signal.cpp | 50 +- server/core/src/miscServerFunct.cpp | 16 + server/core/src/procLog.cpp | 161 - server/core/src/rodsAgent.cpp | 896 --- server/core/src/rodsConnect.cpp | 5 +- server/core/src/rsApiHandler.cpp | 20 + server/delay_server/CMakeLists.txt | 1 + server/delay_server/src/irodsDelayServer.cpp | 312 +- .../include/irods/icatHighLevelRoutines.hpp | 29 + server/icat/src/icatHighLevelRoutines.cpp | 50 + server/irods_grid/CMakeLists.txt | 46 - server/irods_grid/src/irods-grid.cpp | 475 -- server/main_server/CMakeLists.txt | 58 +- .../main_server/include/irods/rodsServer.hpp | 100 - server/main_server/src/agent_main.cpp | 1317 ++++ server/main_server/src/main.cpp | 1153 ++++ .../src/main_server_control_plane.cpp | 1122 ---- server/main_server/src/rodsServer.cpp | 2568 -------- server/re/include/irods/irods_re_plugin.hpp | 72 +- third_party/jsoncons/allocator_holder.hpp | 38 + third_party/jsoncons/allocator_set.hpp | 66 + third_party/jsoncons/basic_json.hpp | 5503 +++++++++++++++++ third_party/jsoncons/bigint.hpp | 1632 +++++ third_party/jsoncons/byte_string.hpp | 805 +++ third_party/jsoncons/config/binary_config.hpp | 226 + .../jsoncons/config/compiler_support.hpp | 417 ++ .../jsoncons/config/jsoncons_config.hpp | 302 + third_party/jsoncons/config/version.hpp | 40 + third_party/jsoncons/conv_error.hpp | 214 + third_party/jsoncons/decode_json.hpp | 211 + third_party/jsoncons/decode_traits.hpp | 651 ++ third_party/jsoncons/detail/endian.hpp | 44 + third_party/jsoncons/detail/grisu3.hpp | 312 + third_party/jsoncons/detail/heap_string.hpp | 198 + third_party/jsoncons/detail/optional.hpp | 487 ++ third_party/jsoncons/detail/parse_number.hpp | 1047 ++++ third_party/jsoncons/detail/span.hpp | 188 + third_party/jsoncons/detail/string_view.hpp | 559 ++ third_party/jsoncons/detail/write_number.hpp | 566 ++ third_party/jsoncons/encode_json.hpp | 290 + third_party/jsoncons/encode_traits.hpp | 378 ++ third_party/jsoncons/extension_traits.hpp | 928 +++ third_party/jsoncons/item_event_visitor.hpp | 2080 +++++++ third_party/jsoncons/json.hpp | 17 + third_party/jsoncons/json_array.hpp | 283 + third_party/jsoncons/json_cursor.hpp | 441 ++ third_party/jsoncons/json_decoder.hpp | 384 ++ third_party/jsoncons/json_encoder.hpp | 1604 +++++ third_party/jsoncons/json_error.hpp | 150 + third_party/jsoncons/json_exception.hpp | 220 + third_party/jsoncons/json_filter.hpp | 609 ++ third_party/jsoncons/json_fwd.hpp | 23 + third_party/jsoncons/json_object.hpp | 1720 ++++++ third_party/jsoncons/json_options.hpp | 689 +++ third_party/jsoncons/json_parser.hpp | 2827 +++++++++ third_party/jsoncons/json_reader.hpp | 419 ++ third_party/jsoncons/json_traits_macros.hpp | 1072 ++++ third_party/jsoncons/json_type.hpp | 219 + third_party/jsoncons/json_type_traits.hpp | 1903 ++++++ third_party/jsoncons/json_visitor.hpp | 1067 ++++ third_party/jsoncons/pretty_print.hpp | 89 + third_party/jsoncons/ser_context.hpp | 39 + third_party/jsoncons/sink.hpp | 289 + third_party/jsoncons/source.hpp | 793 +++ third_party/jsoncons/source_adaptor.hpp | 148 + third_party/jsoncons/staj_cursor.hpp | 746 +++ third_party/jsoncons/staj_event.hpp | 541 ++ third_party/jsoncons/staj_event_reader.hpp | 739 +++ third_party/jsoncons/staj_iterator.hpp | 429 ++ third_party/jsoncons/tag_type.hpp | 232 + third_party/jsoncons/text_source_adaptor.hpp | 144 + third_party/jsoncons/typed_array_view.hpp | 250 + third_party/jsoncons/unicode_traits.hpp | 1330 ++++ third_party/jsoncons/uri.hpp | 1151 ++++ third_party/jsoncons/value_converter.hpp | 340 + third_party/jsoncons_ext/bson/bson.hpp | 23 + third_party/jsoncons_ext/bson/bson_cursor.hpp | 263 + .../jsoncons_ext/bson/bson_decimal128.hpp | 865 +++ .../jsoncons_ext/bson/bson_decimal128.hpp.bak | 816 +++ .../jsoncons_ext/bson/bson_encoder.hpp | 575 ++ third_party/jsoncons_ext/bson/bson_error.hpp | 103 + third_party/jsoncons_ext/bson/bson_oid.hpp | 245 + .../jsoncons_ext/bson/bson_options.hpp | 75 + third_party/jsoncons_ext/bson/bson_parser.hpp | 650 ++ third_party/jsoncons_ext/bson/bson_reader.hpp | 87 + third_party/jsoncons_ext/bson/bson_type.hpp | 44 + third_party/jsoncons_ext/bson/decode_bson.hpp | 202 + third_party/jsoncons_ext/bson/encode_bson.hpp | 144 + third_party/jsoncons_ext/cbor/cbor.hpp | 26 + third_party/jsoncons_ext/cbor/cbor_cursor.hpp | 292 + third_party/jsoncons_ext/cbor/cbor_detail.hpp | 93 + .../jsoncons_ext/cbor/cbor_encoder.hpp | 1744 ++++++ third_party/jsoncons_ext/cbor/cbor_error.hpp | 98 + .../jsoncons_ext/cbor/cbor_event_reader.hpp | 262 + .../jsoncons_ext/cbor/cbor_options.hpp | 104 + third_party/jsoncons_ext/cbor/cbor_parser.hpp | 1962 ++++++ third_party/jsoncons_ext/cbor/cbor_reader.hpp | 111 + third_party/jsoncons_ext/cbor/decode_cbor.hpp | 204 + third_party/jsoncons_ext/cbor/encode_cbor.hpp | 152 + third_party/jsoncons_ext/csv/csv.hpp | 17 + third_party/jsoncons_ext/csv/csv_cursor.hpp | 340 + third_party/jsoncons_ext/csv/csv_encoder.hpp | 942 +++ third_party/jsoncons_ext/csv/csv_error.hpp | 81 + third_party/jsoncons_ext/csv/csv_options.hpp | 944 +++ third_party/jsoncons_ext/csv/csv_parser.hpp | 2099 +++++++ third_party/jsoncons_ext/csv/csv_reader.hpp | 343 + .../jsoncons_ext/csv/csv_serializer.hpp | 12 + third_party/jsoncons_ext/csv/decode_csv.hpp | 209 + third_party/jsoncons_ext/csv/encode_csv.hpp | 122 + .../jsoncons_ext/jmespath/jmespath.hpp | 5213 ++++++++++++++++ .../jsoncons_ext/jmespath/jmespath_error.hpp | 215 + .../jsoncons_ext/jsonpatch/jsonpatch.hpp | 585 ++ .../jsonpatch/jsonpatch_error.hpp | 121 + .../jsoncons_ext/jsonpath/expression.hpp | 3424 ++++++++++ third_party/jsoncons_ext/jsonpath/flatten.hpp | 377 ++ .../jsoncons_ext/jsonpath/json_location.hpp | 956 +++ .../jsonpath/json_location_parser.hpp | 22 + .../jsoncons_ext/jsonpath/json_query.hpp | 217 + .../jsoncons_ext/jsonpath/jsonpath.hpp | 14 + .../jsoncons_ext/jsonpath/jsonpath_error.hpp | 249 + .../jsonpath/jsonpath_expression.hpp | 267 + .../jsoncons_ext/jsonpath/jsonpath_parser.hpp | 2488 ++++++++ .../jsonpath/jsonpath_selector.hpp | 1319 ++++ .../jsonpath/jsonpath_utilities.hpp | 77 + .../jsoncons_ext/jsonpath/path_node.hpp | 339 + .../jsoncons_ext/jsonpointer/jsonpointer.hpp | 1442 +++++ .../jsonpointer/jsonpointer_error.hpp | 119 + .../jsonschema/common/compilation_context.hpp | 97 + .../jsonschema/common/evaluation_context.hpp | 164 + .../jsonschema/common/format_validator.hpp | 1187 ++++ .../jsonschema/common/keyword_validators.hpp | 3968 ++++++++++++ .../jsonschema/common/schema_builder.hpp | 1011 +++ .../jsonschema/common/schema_validators.hpp | 467 ++ .../jsonschema/common/uri_wrapper.hpp | 180 + .../jsonschema/common/validator.hpp | 438 ++ .../draft201909/schema_builder_201909.hpp | 552 ++ .../draft201909/schema_draft201909.hpp | 336 + .../draft202012/schema_builder_202012.hpp | 679 ++ .../draft202012/schema_draft202012.hpp | 364 ++ .../jsonschema/draft4/schema_builder_4.hpp | 463 ++ .../jsonschema/draft4/schema_draft4.hpp | 181 + .../jsonschema/draft6/schema_builder_6.hpp | 413 ++ .../jsonschema/draft6/schema_draft6.hpp | 185 + .../jsonschema/draft7/schema_builder_7.hpp | 449 ++ .../jsonschema/draft7/schema_draft7.hpp | 200 + .../jsonschema/evaluation_options.hpp | 112 + .../jsoncons_ext/jsonschema/json_schema.hpp | 233 + .../jsonschema/json_schema_factory.hpp | 352 ++ .../jsonschema/json_validator.hpp | 176 + .../jsoncons_ext/jsonschema/jsonschema.hpp | 13 + .../jsonschema/jsonschema_error.hpp | 47 + .../jsonschema/validation_message.hpp | 96 + .../jsoncons_ext/mergepatch/mergepatch.hpp | 103 + .../jsoncons_ext/msgpack/decode_msgpack.hpp | 203 + .../jsoncons_ext/msgpack/encode_msgpack.hpp | 142 + third_party/jsoncons_ext/msgpack/msgpack.hpp | 24 + .../jsoncons_ext/msgpack/msgpack_cursor.hpp | 283 + .../jsoncons_ext/msgpack/msgpack_encoder.hpp | 742 +++ .../jsoncons_ext/msgpack/msgpack_error.hpp | 94 + .../msgpack/msgpack_event_reader.hpp | 256 + .../jsoncons_ext/msgpack/msgpack_options.hpp | 74 + .../jsoncons_ext/msgpack/msgpack_parser.hpp | 748 +++ .../jsoncons_ext/msgpack/msgpack_reader.hpp | 111 + .../jsoncons_ext/msgpack/msgpack_type.hpp | 63 + .../jsoncons_ext/ubjson/decode_ubjson.hpp | 202 + .../jsoncons_ext/ubjson/encode_ubjson.hpp | 142 + third_party/jsoncons_ext/ubjson/ubjson.hpp | 23 + .../jsoncons_ext/ubjson/ubjson_cursor.hpp | 250 + .../jsoncons_ext/ubjson/ubjson_encoder.hpp | 494 ++ .../jsoncons_ext/ubjson/ubjson_error.hpp | 100 + .../jsoncons_ext/ubjson/ubjson_options.hpp | 87 + .../jsoncons_ext/ubjson/ubjson_parser.hpp | 880 +++ .../jsoncons_ext/ubjson/ubjson_reader.hpp | 87 + .../jsoncons_ext/ubjson/ubjson_type.hpp | 43 + version.json.dist.in | 4 +- 272 files changed, 91486 insertions(+), 7009 deletions(-) create mode 100644 lib/api/include/irods/delay_rule_tag.h create mode 100644 lib/api/include/irods/delay_rule_tag_clear.h create mode 100644 lib/api/include/irods/get_active_agents.h create mode 100644 lib/api/src/rc_delay_rule_tag.cpp create mode 100644 lib/api/src/rc_delay_rule_tag_clear.cpp delete mode 100644 schemas/README.md delete mode 100644 schemas/configuration/v4/grid_status.json.in rename schemas/configuration/{v4 => v5}/client_environment.json.in (76%) rename schemas/configuration/{v4 => v5}/client_hints.json.in (82%) rename schemas/configuration/{v4 => v5}/configuration_directory.json.in (82%) rename schemas/configuration/{v4 => v5}/database_config.json.in (83%) rename schemas/configuration/{v4 => v5}/host_access_control.json.in (82%) rename schemas/configuration/{v4 => v5}/host_resolution.json.in (86%) rename schemas/configuration/{v4 => v5}/plugin.json.in (76%) rename schemas/configuration/{v4 => v5}/resource.json.in (88%) rename schemas/configuration/{v4 => v5}/rule_engine.json.in (80%) rename schemas/configuration/{v4 => v5}/server.json.in (92%) rename schemas/configuration/{v4 => v5}/server_config.json.in (94%) rename schemas/configuration/{v4 => v5}/server_status.json.in (85%) rename schemas/configuration/{v4 => v5}/service_account_environment.json.in (96%) rename schemas/configuration/{v4 => v5}/unattended_installation.json.in (87%) rename schemas/configuration/{v4 => v5}/version.json.in (93%) rename schemas/configuration/{v4 => v5}/zone_bundle.json.in (88%) delete mode 100644 schemas/messaging/v1/server_control_plane_command.json delete mode 100644 scripts/irods/json_validation.py delete mode 100644 scripts/irods/test/test_control_plane.py delete mode 100755 scripts/pid_age.py delete mode 100644 scripts/system_identification.py delete mode 100755 scripts/validate_json.py create mode 100644 server/api/include/irods/rs_delay_rule_tag.hpp create mode 100644 server/api/include/irods/rs_delay_rule_tag_clear.hpp create mode 100644 server/api/src/rs_delay_rule_tag.cpp create mode 100644 server/api/src/rs_delay_rule_tag_clear.cpp delete mode 100644 server/control_plane/CMakeLists.txt delete mode 100644 server/control_plane/include/irods/irods_server_control_plane.hpp create mode 100644 server/core/include/irods/agent_globals.hpp delete mode 100644 server/core/include/irods/procLog.h delete mode 100644 server/core/include/irods/rodsAgent.hpp delete mode 100644 server/core/src/procLog.cpp delete mode 100644 server/core/src/rodsAgent.cpp delete mode 100644 server/irods_grid/CMakeLists.txt delete mode 100644 server/irods_grid/src/irods-grid.cpp delete mode 100644 server/main_server/include/irods/rodsServer.hpp create mode 100644 server/main_server/src/agent_main.cpp create mode 100644 server/main_server/src/main.cpp delete mode 100644 server/main_server/src/main_server_control_plane.cpp delete mode 100644 server/main_server/src/rodsServer.cpp create mode 100644 third_party/jsoncons/allocator_holder.hpp create mode 100644 third_party/jsoncons/allocator_set.hpp create mode 100644 third_party/jsoncons/basic_json.hpp create mode 100644 third_party/jsoncons/bigint.hpp create mode 100644 third_party/jsoncons/byte_string.hpp create mode 100644 third_party/jsoncons/config/binary_config.hpp create mode 100644 third_party/jsoncons/config/compiler_support.hpp create mode 100644 third_party/jsoncons/config/jsoncons_config.hpp create mode 100644 third_party/jsoncons/config/version.hpp create mode 100644 third_party/jsoncons/conv_error.hpp create mode 100644 third_party/jsoncons/decode_json.hpp create mode 100644 third_party/jsoncons/decode_traits.hpp create mode 100644 third_party/jsoncons/detail/endian.hpp create mode 100644 third_party/jsoncons/detail/grisu3.hpp create mode 100644 third_party/jsoncons/detail/heap_string.hpp create mode 100644 third_party/jsoncons/detail/optional.hpp create mode 100644 third_party/jsoncons/detail/parse_number.hpp create mode 100644 third_party/jsoncons/detail/span.hpp create mode 100644 third_party/jsoncons/detail/string_view.hpp create mode 100644 third_party/jsoncons/detail/write_number.hpp create mode 100644 third_party/jsoncons/encode_json.hpp create mode 100644 third_party/jsoncons/encode_traits.hpp create mode 100644 third_party/jsoncons/extension_traits.hpp create mode 100644 third_party/jsoncons/item_event_visitor.hpp create mode 100644 third_party/jsoncons/json.hpp create mode 100644 third_party/jsoncons/json_array.hpp create mode 100644 third_party/jsoncons/json_cursor.hpp create mode 100644 third_party/jsoncons/json_decoder.hpp create mode 100644 third_party/jsoncons/json_encoder.hpp create mode 100644 third_party/jsoncons/json_error.hpp create mode 100644 third_party/jsoncons/json_exception.hpp create mode 100644 third_party/jsoncons/json_filter.hpp create mode 100644 third_party/jsoncons/json_fwd.hpp create mode 100644 third_party/jsoncons/json_object.hpp create mode 100644 third_party/jsoncons/json_options.hpp create mode 100644 third_party/jsoncons/json_parser.hpp create mode 100644 third_party/jsoncons/json_reader.hpp create mode 100644 third_party/jsoncons/json_traits_macros.hpp create mode 100644 third_party/jsoncons/json_type.hpp create mode 100644 third_party/jsoncons/json_type_traits.hpp create mode 100644 third_party/jsoncons/json_visitor.hpp create mode 100644 third_party/jsoncons/pretty_print.hpp create mode 100644 third_party/jsoncons/ser_context.hpp create mode 100644 third_party/jsoncons/sink.hpp create mode 100644 third_party/jsoncons/source.hpp create mode 100644 third_party/jsoncons/source_adaptor.hpp create mode 100644 third_party/jsoncons/staj_cursor.hpp create mode 100644 third_party/jsoncons/staj_event.hpp create mode 100644 third_party/jsoncons/staj_event_reader.hpp create mode 100644 third_party/jsoncons/staj_iterator.hpp create mode 100644 third_party/jsoncons/tag_type.hpp create mode 100644 third_party/jsoncons/text_source_adaptor.hpp create mode 100644 third_party/jsoncons/typed_array_view.hpp create mode 100644 third_party/jsoncons/unicode_traits.hpp create mode 100644 third_party/jsoncons/uri.hpp create mode 100644 third_party/jsoncons/value_converter.hpp create mode 100644 third_party/jsoncons_ext/bson/bson.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_cursor.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_decimal128.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_decimal128.hpp.bak create mode 100644 third_party/jsoncons_ext/bson/bson_encoder.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_error.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_oid.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_options.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_parser.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_reader.hpp create mode 100644 third_party/jsoncons_ext/bson/bson_type.hpp create mode 100644 third_party/jsoncons_ext/bson/decode_bson.hpp create mode 100644 third_party/jsoncons_ext/bson/encode_bson.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_cursor.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_detail.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_encoder.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_error.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_event_reader.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_options.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_parser.hpp create mode 100644 third_party/jsoncons_ext/cbor/cbor_reader.hpp create mode 100644 third_party/jsoncons_ext/cbor/decode_cbor.hpp create mode 100644 third_party/jsoncons_ext/cbor/encode_cbor.hpp create mode 100644 third_party/jsoncons_ext/csv/csv.hpp create mode 100644 third_party/jsoncons_ext/csv/csv_cursor.hpp create mode 100644 third_party/jsoncons_ext/csv/csv_encoder.hpp create mode 100644 third_party/jsoncons_ext/csv/csv_error.hpp create mode 100644 third_party/jsoncons_ext/csv/csv_options.hpp create mode 100644 third_party/jsoncons_ext/csv/csv_parser.hpp create mode 100644 third_party/jsoncons_ext/csv/csv_reader.hpp create mode 100644 third_party/jsoncons_ext/csv/csv_serializer.hpp create mode 100644 third_party/jsoncons_ext/csv/decode_csv.hpp create mode 100644 third_party/jsoncons_ext/csv/encode_csv.hpp create mode 100644 third_party/jsoncons_ext/jmespath/jmespath.hpp create mode 100644 third_party/jsoncons_ext/jmespath/jmespath_error.hpp create mode 100644 third_party/jsoncons_ext/jsonpatch/jsonpatch.hpp create mode 100644 third_party/jsoncons_ext/jsonpatch/jsonpatch_error.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/expression.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/flatten.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/json_location.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/json_location_parser.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/json_query.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/jsonpath.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/jsonpath_error.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/jsonpath_expression.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/jsonpath_parser.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/jsonpath_selector.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/jsonpath_utilities.hpp create mode 100644 third_party/jsoncons_ext/jsonpath/path_node.hpp create mode 100644 third_party/jsoncons_ext/jsonpointer/jsonpointer.hpp create mode 100644 third_party/jsoncons_ext/jsonpointer/jsonpointer_error.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/compilation_context.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/evaluation_context.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/format_validator.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/keyword_validators.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/schema_builder.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/schema_validators.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/uri_wrapper.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/common/validator.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft201909/schema_builder_201909.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft201909/schema_draft201909.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft202012/schema_builder_202012.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft202012/schema_draft202012.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft4/schema_builder_4.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft4/schema_draft4.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft6/schema_builder_6.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft6/schema_draft6.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft7/schema_builder_7.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/draft7/schema_draft7.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/evaluation_options.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/json_schema.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/json_schema_factory.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/json_validator.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/jsonschema.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/jsonschema_error.hpp create mode 100644 third_party/jsoncons_ext/jsonschema/validation_message.hpp create mode 100644 third_party/jsoncons_ext/mergepatch/mergepatch.hpp create mode 100644 third_party/jsoncons_ext/msgpack/decode_msgpack.hpp create mode 100644 third_party/jsoncons_ext/msgpack/encode_msgpack.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_cursor.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_encoder.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_error.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_event_reader.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_options.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_parser.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_reader.hpp create mode 100644 third_party/jsoncons_ext/msgpack/msgpack_type.hpp create mode 100644 third_party/jsoncons_ext/ubjson/decode_ubjson.hpp create mode 100644 third_party/jsoncons_ext/ubjson/encode_ubjson.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson_cursor.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson_encoder.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson_error.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson_options.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson_parser.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson_reader.hpp create mode 100644 third_party/jsoncons_ext/ubjson/ubjson_type.hpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 775c184468..ecf9e5d5d7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -99,6 +99,11 @@ set(IRODS_VERSION_PATCH "0") set(IRODS_VERSION "${IRODS_VERSION_MAJOR}.${IRODS_VERSION_MINOR}.${IRODS_VERSION_PATCH}") set(IRODS_PACKAGE_REVISION "0") +# Bump this variable anytime the catalog schema is modified. +set(IRODS_CATALOG_SCHEMA_VERSION "12") +# Bump this variable anytime a JSON schema is modified. +set(IRODS_CONFIGURATION_SCHEMA_VERSION "5") + project(irods VERSION ${IRODS_VERSION} LANGUAGES C CXX) diff --git a/lib/api/CMakeLists.txt b/lib/api/CMakeLists.txt index 6b66e83b9e..aaa78ca595 100644 --- a/lib/api/CMakeLists.txt +++ b/lib/api/CMakeLists.txt @@ -139,6 +139,8 @@ foreach(variant IN ITEMS client server) "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_check_auth_credentials.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_data_object_finalize.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_data_object_modify_info.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_delay_rule_tag.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_delay_rule_tag_clear.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_genquery2.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_get_delay_rule_info.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rc_get_file_descriptor_info.cpp" @@ -249,6 +251,8 @@ install( "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/dataPut.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/data_object_finalize.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/data_object_modify_info.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/delay_rule_tag.h" + "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/delay_rule_tag_clear.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/endTransaction.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/execCmd.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/execMyRule.h" diff --git a/lib/api/include/irods/apiHeaderAll.h b/lib/api/include/irods/apiHeaderAll.h index 50b9571ce4..6f1bf1f134 100644 --- a/lib/api/include/irods/apiHeaderAll.h +++ b/lib/api/include/irods/apiHeaderAll.h @@ -151,4 +151,7 @@ #include "irods/replica_truncate.h" +#include "irods/delay_rule_tag.h" +#include "irods/delay_rule_tag_clear.h" + #endif // API_HEADER_ALL_H__ diff --git a/lib/api/include/irods/apiNumberData.h b/lib/api/include/irods/apiNumberData.h index 7d74a01e6d..beb58f9900 100644 --- a/lib/api/include/irods/apiNumberData.h +++ b/lib/api/include/irods/apiNumberData.h @@ -166,5 +166,7 @@ API_NUMBER(CLIENT_HINTS_AN, 10215) API_NUMBER(GET_RESOURCE_INFO_FOR_OPERATION_AN, 10220) API_NUMBER(GENQUERY2_AN, 10221) +API_NUMBER(DELAY_RULE_TAG_AN, 10222) +API_NUMBER(DELAY_RULE_TAG_CLEAR_AN, 10223) // clang-format on diff --git a/lib/api/include/irods/apiTable.hpp b/lib/api/include/irods/apiTable.hpp index 089106ed9b..b919af7ec1 100644 --- a/lib/api/include/irods/apiTable.hpp +++ b/lib/api/include/irods/apiTable.hpp @@ -151,6 +151,8 @@ # include "irods/rsUserAdmin.hpp" # include "irods/rsZoneReport.hpp" # include "irods/rs_check_auth_credentials.hpp" +# include "irods/rs_delay_rule_tag.hpp" +# include "irods/rs_delay_rule_tag_clear.hpp" # include "irods/rs_genquery2.hpp" # include "irods/rs_get_library_features.hpp" # include "irods/rs_get_resource_info_for_operation.hpp" @@ -205,6 +207,8 @@ #define RS_DATA_OBJ_UNLOCK NULLPTR_FOR_CLIENT_TABLE(rsDataObjUnlock) #define RS_DATA_OBJ_WRITE NULLPTR_FOR_CLIENT_TABLE(rsDataObjWrite) #define RS_DATA_PUT NULLPTR_FOR_CLIENT_TABLE(rsDataPut) +#define RS_DELAY_RULE_TAG NULLPTR_FOR_CLIENT_TABLE(rs_delay_rule_tag) +#define RS_DELAY_RULE_TAG_CLEAR NULLPTR_FOR_CLIENT_TABLE(rs_delay_rule_tag_clear) #define RS_END_TRANSACTION NULLPTR_FOR_CLIENT_TABLE(rsEndTransaction) #define RS_EXEC_CMD NULLPTR_FOR_CLIENT_TABLE(rsExecCmd) #define RS_EXEC_MY_RULE NULLPTR_FOR_CLIENT_TABLE(rsExecMyRule) @@ -1299,6 +1303,20 @@ static irods::apidef_t client_api_table_inp[] = { boost::any(std::function(RS_GENQUERY2)), "api_genquery2", clearGenquery2Input, irods::clearOutStruct_noop, (funcPtr) CALL_GENQUERY2_INOUT + }, + { + DELAY_RULE_TAG_AN, RODS_API_VERSION, LOCAL_PRIV_USER_AUTH, LOCAL_PRIV_USER_AUTH, + "DelayRuleTagInput_PI", 0, nullptr, 0, + boost::any(std::function(RS_DELAY_RULE_TAG)), + "api_delay_rule_tag", clearDelayRuleTagInput, irods::clearOutStruct_noop, + (funcPtr) CALL_DELAY_RULE_TAG + }, + { + DELAY_RULE_TAG_CLEAR_AN, RODS_API_VERSION, LOCAL_PRIV_USER_AUTH, LOCAL_PRIV_USER_AUTH, + "DelayRuleTagClearInput_PI", 0, nullptr, 0, + boost::any(std::function(RS_DELAY_RULE_TAG_CLEAR)), + "api_delay_rule_tag_clear", clearDelayRuleTagClearInput, irods::clearOutStruct_noop, + (funcPtr) CALL_DELAY_RULE_TAG_CLEAR } // clang-format on }; // _api_table_inp diff --git a/lib/api/include/irods/api_pack_table.hpp b/lib/api/include/irods/api_pack_table.hpp index acb8ced7ac..1933fa77ce 100644 --- a/lib/api/include/irods/api_pack_table.hpp +++ b/lib/api/include/irods/api_pack_table.hpp @@ -94,6 +94,8 @@ inline const packInstruct_t api_pack_table_init[] = { {"ExecRuleExpression_PI", ExecRuleExpression_PI, irods::clearInStruct_noop}, {"CheckAuthCredentialsInput_PI", CheckAuthCredentialsInput_PI, irods::clearInStruct_noop}, {"Genquery2Input_PI", Genquery2Input_PI, irods::clearInStruct_noop}, + {"DelayRuleTagInput_PI", DelayRuleTagInput_PI, irods::clearInStruct_noop}, + {"DelayRuleTagClearInput_PI", DelayRuleTagClearInput_PI, irods::clearInStruct_noop}, {PACK_TABLE_END_PI, nullptr, irods::clearInStruct_noop}, }; diff --git a/lib/api/include/irods/delay_rule_tag.h b/lib/api/include/irods/delay_rule_tag.h new file mode 100644 index 0000000000..edae8fc70b --- /dev/null +++ b/lib/api/include/irods/delay_rule_tag.h @@ -0,0 +1,43 @@ +#ifndef IRODS_DELAY_RULE_TAG_H +#define IRODS_DELAY_RULE_TAG_H + +/// \file + +#include "irods/objInfo.h" + +struct RcComm; + +/// TODO +/// +/// \since 5.0.0 +typedef struct DelayRuleTagInput +{ + char rule_id[32]; // NOLINT(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) + char tag[300]; // NOLINT(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) + struct KeyValPair condInput; +} delayRuleTagInp_t; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DelayRuleTagInput_PI "str rule_id[32]; str tag[300]; struct KeyValPair_PI;" + +#ifdef __cplusplus +extern "C" { +#endif + +/// TODO +/// +/// \param[in] _comm A pointer to a RcComm. +/// \param[in] _input +/// +/// \return An integer. +/// \retval 0 On success. +/// \retval non-zero On failure. +/// +/// \since 5.0.0 +int rc_delay_rule_tag(struct RcComm* _comm, struct DelayRuleTagInput* _input); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // IRODS_DELAY_RULE_TAG_H diff --git a/lib/api/include/irods/delay_rule_tag_clear.h b/lib/api/include/irods/delay_rule_tag_clear.h new file mode 100644 index 0000000000..c495ed618c --- /dev/null +++ b/lib/api/include/irods/delay_rule_tag_clear.h @@ -0,0 +1,42 @@ +#ifndef IRODS_DELAY_RULE_TAG_CLEAR_H +#define IRODS_DELAY_RULE_TAG_CLEAR_H + +/// \file + +#include "irods/objInfo.h" + +struct RcComm; + +/// TODO +/// +/// \since 5.0.0 +typedef struct DelayRuleTagClearInput +{ + char rule_id[32]; // NOLINT(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) + struct KeyValPair condInput; +} delayRuleTagClearInp_t; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define DelayRuleTagClearInput_PI "str rule_id[32]; str tag[32]; struct KeyValPair_PI;" + +#ifdef __cplusplus +extern "C" { +#endif + +/// TODO +/// +/// \param[in] _comm A pointer to a RcComm. +/// \param[in] _input +/// +/// \return An integer. +/// \retval 0 On success. +/// \retval non-zero On failure. +/// +/// \since 5.0.0 +int rc_delay_rule_tag_clear(struct RcComm* _comm, struct DelayRuleTagClearInput* _input); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // IRODS_DELAY_RULE_TAG_CLEAR_H diff --git a/lib/api/include/irods/get_active_agents.h b/lib/api/include/irods/get_active_agents.h new file mode 100644 index 0000000000..aac5837276 --- /dev/null +++ b/lib/api/include/irods/get_active_agents.h @@ -0,0 +1,44 @@ +#ifndef IRODS_GET_ACTIVE_AGENTS_H +#define IRODS_GET_ACTIVE_AGENTS_H + +/// \file + +#include "irods/objInfo.h" + +struct RcComm; + +/// TODO +/// +/// \since 5.0.0 +typedef struct GetActiveAgentsInput +{ + char hostname[65]; // NOLINT(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) + char zone[250]; // NOLINT(cppcoreguidelines-avoid-c-arrays, modernize-avoid-c-arrays) + struct KeyValPair condInput; +} getActiveAgentsInp_t; + +// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) +#define GetActiveAgentsInput_PI "str hostname[65]; str zone[250]; struct KeyValPair_PI;" + +#ifdef __cplusplus +extern "C" { +#endif + +/// TODO +/// +/// \param[in] _comm A pointer to a RcComm. +/// \param[in] _input +/// \param[in,out] _info +/// +/// \return An integer. +/// \retval 0 On success. +/// \retval non-zero On failure. +/// +/// \since 5.0.0 +int rc_get_active_agents(struct RcComm* _comm, struct GetActiveAgentsInput* _input, char** _info); + +#ifdef __cplusplus +} // extern "C" +#endif + +#endif // IRODS_GET_ACTIVE_AGENTS_H diff --git a/lib/api/src/rc_delay_rule_tag.cpp b/lib/api/src/rc_delay_rule_tag.cpp new file mode 100644 index 0000000000..28b263e2f8 --- /dev/null +++ b/lib/api/src/rc_delay_rule_tag.cpp @@ -0,0 +1,14 @@ +#include "irods/delay_rule_tag.h" + +#include "irods/apiNumber.h" +#include "irods/procApiRequest.h" +#include "irods/rodsErrorTable.h" + +auto rc_delay_rule_tag(RcComm* _comm, DelayRuleTagInput* _input) -> int +{ + if (!_comm || !_input) { + return SYS_INVALID_INPUT_PARAM; + } + + return procApiRequest(_comm, DELAY_RULE_TAG_AN, _input, nullptr, nullptr, nullptr); +} // rc_delay_rule_tag diff --git a/lib/api/src/rc_delay_rule_tag_clear.cpp b/lib/api/src/rc_delay_rule_tag_clear.cpp new file mode 100644 index 0000000000..64433e49b2 --- /dev/null +++ b/lib/api/src/rc_delay_rule_tag_clear.cpp @@ -0,0 +1,14 @@ +#include "irods/delay_rule_tag_clear.h" + +#include "irods/apiNumber.h" +#include "irods/procApiRequest.h" +#include "irods/rodsErrorTable.h" + +auto rc_delay_rule_tag_clear(RcComm* _comm, DelayRuleTagClearInput* _input) -> int +{ + if (!_comm || !_input) { + return SYS_INVALID_INPUT_PARAM; + } + + return procApiRequest(_comm, DELAY_RULE_TAG_CLEAR_AN, _input, nullptr, nullptr, nullptr); +} // rc_delay_rule_tag_clear diff --git a/lib/core/include/irods/dns_cache.hpp b/lib/core/include/irods/dns_cache.hpp index b43be8ab14..a2be32067c 100644 --- a/lib/core/include/irods/dns_cache.hpp +++ b/lib/core/include/irods/dns_cache.hpp @@ -35,6 +35,20 @@ namespace irods::experimental::net::dns_cache /// \since 4.2.9 auto deinit() noexcept -> void; + /// Initializes the dns cache from an existing shared memory object. + /// + /// This function should only be called on startup of the server. + /// + /// \param[in] _shm_name The name of the shared memory to create. + /// + /// \since 5.0.0 + auto init_no_create(const std::string_view _shm_name) -> void; + + /// TODO + /// + /// \since 5.0.0 + auto shared_memory_name() -> std::string_view; + /// Inserts a new mapping or updates an existing mapping within the DNS cache. /// /// \param[in] _key The key that will be mapped to \p _info. diff --git a/lib/core/include/irods/hostname_cache.hpp b/lib/core/include/irods/hostname_cache.hpp index 42537ea6f0..953069bdc5 100644 --- a/lib/core/include/irods/hostname_cache.hpp +++ b/lib/core/include/irods/hostname_cache.hpp @@ -28,6 +28,20 @@ namespace irods::experimental::net::hostname_cache /// \since 4.2.9 auto deinit() noexcept -> void; + /// Initializes the dns cache from an existing shared memory object. + /// + /// This function should only be called on startup of the server. + /// + /// \param[in] _shm_name The name of the shared memory to create. + /// + /// \since 5.0.0 + auto init_no_create(const std::string_view _shm_name) -> void; + + /// TODO + /// + /// \since 5.0.0 + auto shared_memory_name() -> std::string_view; + /// Inserts a new mapping or updates an existing mapping within the hostname cache. /// /// \param[in] _key The key that will be mapped to \p _alias. diff --git a/lib/core/include/irods/irods_client_server_negotiation.hpp b/lib/core/include/irods/irods_client_server_negotiation.hpp index 8dc38e2fec..d4a976108c 100644 --- a/lib/core/include/irods/irods_client_server_negotiation.hpp +++ b/lib/core/include/irods/irods_client_server_negotiation.hpp @@ -57,7 +57,7 @@ namespace irods /// =-=-=-=-=-=-=- /// @brief function which determines if a client/server negotiation is needed /// on the server side - bool do_client_server_negotiation_for_server(); + bool do_client_server_negotiation_for_server(const char* _neg); /// =-=-=-=-=-=-=- /// @brief function which determines if a client/server negotiation is needed @@ -68,7 +68,9 @@ namespace irods /// @brief function which manages the TLS and Auth negotiations with the client error client_server_negotiation_for_server( irods::network_object_ptr, // server connection handle - std::string& ); // results of negotiation + std::string&, // results of negotiation + bool, // boolean indicating whether client-server negotiation is needed + RsComm* = nullptr); // optional RsComm initialized during agent startup (maintains compatibility with rodsAgent.cpp) /// =-=-=-=-=-=-=- /// @brief function which manages the TLS and Auth negotiations with the client diff --git a/lib/core/include/irods/irods_default_paths.hpp.in b/lib/core/include/irods/irods_default_paths.hpp.in index 362f65e35b..88bf867c73 100644 --- a/lib/core/include/irods/irods_default_paths.hpp.in +++ b/lib/core/include/irods/irods_default_paths.hpp.in @@ -3,24 +3,34 @@ #include -#define IRODS_DEFAULT_PATH_LIBDIR "@CMAKE_INSTALL_LIBDIR@" -#define IRODS_DEFAULT_PATH_SYSCONFDIR "@CMAKE_INSTALL_SYSCONFDIR@" -#define IRODS_DEFAULT_PATH_HOMEDIR "@IRODS_HOME_DIRECTORY@" -#define IRODS_DEFAULT_PATH_PLUGINDIR "@IRODS_PLUGINS_DIRECTORY@" +#define IRODS_DEFAULT_PATH_SBINDIR "@CMAKE_INSTALL_SBINDIR@" +#define IRODS_DEFAULT_PATH_LIBDIR "@CMAKE_INSTALL_LIBDIR@" +#define IRODS_DEFAULT_PATH_SYSCONFDIR "@CMAKE_INSTALL_SYSCONFDIR@" +#define IRODS_DEFAULT_PATH_RUNSTATEDIR "@CMAKE_INSTALL_RUNSTATEDIR@" +#define IRODS_DEFAULT_PATH_HOMEDIR "@IRODS_HOME_DIRECTORY@" +#define IRODS_DEFAULT_PATH_PLUGINDIR "@IRODS_PLUGINS_DIRECTORY@" namespace irods { boost::filesystem::path get_irods_root_directory(); + boost::filesystem::path get_irods_sbin_directory(); + boost::filesystem::path get_irods_lib_directory(); boost::filesystem::path get_irods_config_directory(); + boost::filesystem::path get_irods_runstate_directory(); + boost::filesystem::path get_irods_home_directory(); boost::filesystem::path get_irods_default_plugin_directory(); boost::filesystem::path get_irods_stacktrace_directory(); + + boost::filesystem::path get_irods_proc_directory(); + + boost::filesystem::path get_irods_msiExecCmd_bin_directory(); } // namespace irods #endif // IRODS_DEFAULT_PATHS_HPP diff --git a/lib/core/include/irods/irods_load_plugin.hpp b/lib/core/include/irods/irods_load_plugin.hpp index 8a8a0af597..f123b3daa2 100644 --- a/lib/core/include/irods/irods_load_plugin.hpp +++ b/lib/core/include/irods/irods_load_plugin.hpp @@ -1,20 +1,18 @@ -#ifndef __IRODS_LOAD_PLUGIN_HPP__ -#define __IRODS_LOAD_PLUGIN_HPP__ +#ifndef IRODS_LOAD_PLUGIN_HPP +#define IRODS_LOAD_PLUGIN_HPP -// =-=-=-=-=-=-=- -// My Includes -#include "irods/irods_log.hpp" #include "irods/irods_logger.hpp" #include "irods/irods_plugin_name_generator.hpp" #include "irods/irods_configuration_keywords.hpp" #include "irods/getRodsEnv.h" +#include "irods/irods_server_properties.hpp" +#include "irods/rcGlobalExtern.h" #include "irods/rodsErrorTable.h" #include "irods/irods_default_paths.hpp" #include "irods/irods_exception.hpp" -// =-=-=-=-=-=-=- -// STL Includes #include +#include #include #include #include @@ -24,13 +22,9 @@ #include #include -// =-=-=-=-=-=-=- -// Boost Includes #include #include -// =-=-=-=-=-=-=- -// dlopen, etc #include #include @@ -42,20 +36,30 @@ namespace irods fs::path plugin_home; - rodsEnv env; - int status = getRodsEnv( &env ); - if ( !status ) { - if ( strlen( env.irodsPluginHome ) > 0 ) { - plugin_home = env.irodsPluginHome; + // If we're the server, use the plugin directory property from server_config.json when defined. + // TODO Document this in docs.irods.org. + if (SERVER_PT == ::ProcessType || AGENT_PT == ::ProcessType) { + // TODO Document this. + // TODO Add new keyword variable for this. + if (irods::server_property_exists("plugin_directory")) { + plugin_home = irods::get_server_property("plugin_directory"); + } + } + else { + rodsEnv env; + int status = getRodsEnv( &env ); + if ( !status ) { + if ( strlen( env.irodsPluginHome ) > 0 ) { + plugin_home = env.irodsPluginHome; + } } - } if (plugin_home.empty()) { try { plugin_home = get_irods_default_plugin_directory(); - } catch (const irods::exception& e) { - irods::log(e); + } + catch (const irods::exception& e) { return ERROR(SYS_INVALID_INPUT_PARAM, "failed to get default plugin directory"); } } @@ -77,17 +81,12 @@ namespace irods _path = plugin_home.string(); + // Append a trailing path separator if missing. if ( fs::path::preferred_separator != *_path.rbegin() ) { _path += fs::path::preferred_separator; } - rodsLog( - LOG_DEBUG, - "resolved plugin home [%s]", - _path.c_str() ); - return SUCCESS(); - } catch ( const fs::filesystem_error& _e ) { std::string msg( "does not exist [" ); @@ -514,4 +513,4 @@ namespace irods } // load_plugin } // namespace irods -#endif // __IRODS_LOAD_PLUGIN_HPP__ +#endif // IRODS_LOAD_PLUGIN_HPP diff --git a/lib/core/include/irods/irods_logger.hpp b/lib/core/include/irods/irods_logger.hpp index fe0ef94884..b18a3bdc8a 100644 --- a/lib/core/include/irods/irods_logger.hpp +++ b/lib/core/include/irods/irods_logger.hpp @@ -167,13 +167,14 @@ namespace irods::experimental::log /// This function must be called before invoking other logging API operations. /// This function is not thread-safe. /// + /// \param[in] _pid The PID of the main iRODS server process. /// \param[in] _write_to_stdout Configures the logging API to write all messages to stdout. /// \param[in] _enable_test_mode Configures the logging API to also write messages to a special /// file that flushes its output on every write. This is good for /// tests that need to search the log file for specific messages. /// /// \since 4.3.0 - auto init(bool _write_to_stdout = false, bool _enable_test_mode = false) noexcept -> void; + auto init(pid_t _pid, bool _write_to_stdout = false, bool _enable_test_mode = false) noexcept -> void; /// Converts a string to a specific log level. /// diff --git a/lib/core/include/irods/irods_server_properties.hpp b/lib/core/include/irods/irods_server_properties.hpp index d6a39867fc..9a1b4a1343 100644 --- a/lib/core/include/irods/irods_server_properties.hpp +++ b/lib/core/include/irods/irods_server_properties.hpp @@ -53,9 +53,16 @@ namespace irods /// @brief The singleton static server_properties& instance(); + server_properties(const server_properties&) = delete; + server_properties& operator=(const server_properties&) = delete; + + void init(const std::string& _path); + /// @brief Read server configuration and fill server_properties::properties void capture(); + void set_configuration(nlohmann::json _config); + /// \brief Read server configuration, replacing existing keys instead of deleting them. /// \returns a json array containing a json patch nlohmann::json reload(); @@ -211,12 +218,10 @@ namespace irods return std::unique_lock(property_mutex_); } - server_properties(); - - server_properties(const server_properties&) = delete; - server_properties& operator=(const server_properties&) = delete; + server_properties() = default; nlohmann::json config_props_; + std::string file_path_; mutable std::shared_mutex property_mutex_; }; // class server_properties diff --git a/lib/core/include/irods/irods_version.h.in b/lib/core/include/irods/irods_version.h.in index 010c0a8aa7..43a2ce9b3e 100644 --- a/lib/core/include/irods/irods_version.h.in +++ b/lib/core/include/irods/irods_version.h.in @@ -1,10 +1,14 @@ #ifndef IRODS_VERSION_H__ #define IRODS_VERSION_H__ -#define IRODS_VERSION_MAJOR @IRODS_VERSION_MAJOR@ -#define IRODS_VERSION_MINOR @IRODS_VERSION_MINOR@ -#define IRODS_VERSION_PATCHLEVEL @IRODS_VERSION_PATCH@ +#define IRODS_VERSION_MAJOR @IRODS_VERSION_MAJOR@ +#define IRODS_VERSION_MINOR @IRODS_VERSION_MINOR@ +#define IRODS_VERSION_PATCHLEVEL @IRODS_VERSION_PATCH@ -#define IRODS_VERSION_INTEGER (@IRODS_VERSION_MAJOR@*1000000 + @IRODS_VERSION_MINOR@*1000 + @IRODS_VERSION_PATCH@) +#define IRODS_VERSION_INTEGER (@IRODS_VERSION_MAJOR@*1000000 + @IRODS_VERSION_MINOR@*1000 + @IRODS_VERSION_PATCH@) + +#define IRODS_GIT_COMMIT "@IRODS_GIT_SHA1@" +#define IRODS_CATALOG_SCHEMA_VERSION @IRODS_CATALOG_SCHEMA_VERSION@ +#define IRODS_CONFIGURATION_SCHEMA_VERSION @IRODS_CONFIGURATION_SCHEMA_VERSION@ #endif // IRODS_VERSION_H__ diff --git a/lib/core/include/irods/rcMisc.h b/lib/core/include/irods/rcMisc.h index 56a7a6b1e7..0194dc8e20 100644 --- a/lib/core/include/irods/rcMisc.h +++ b/lib/core/include/irods/rcMisc.h @@ -71,6 +71,10 @@ void clearRescQuotaInp(void* _p); void clearGenquery2Input(void* _p); +void clearDelayRuleTagInput(void* _p); + +void clearDelayRuleTagClearInput(void* _p); + // clang-format off __attribute__((deprecated("SimpleQuery is deprecated. Use GenQuery or SpecificQuery instead."))) void clearSimpleQueryOut(void* _p); diff --git a/lib/core/include/irods/rodsErrorTable.h b/lib/core/include/irods/rodsErrorTable.h index 7f793db949..380d7b8d55 100644 --- a/lib/core/include/irods/rodsErrorTable.h +++ b/lib/core/include/irods/rodsErrorTable.h @@ -246,6 +246,8 @@ NEW_ERROR(TYPE_NOT_SUPPORTED, -177000) NEW_ERROR(AUTHENTICATION_ERROR, -178000) NEW_ERROR(SOCKET_ERROR, -179000) NEW_ERROR(CONFIGURATION_ERROR, -180000) +NEW_ERROR(SHUTDOWN_SEQUENCE_INITIATED, -181000) +NEW_ERROR(INTERRUPT_DETECTED, -182000) /** @} */ @@ -500,6 +502,7 @@ NEW_ERROR(CAT_INVALID_RESOURCE_NAME, -859000) // JMC NEW_ERROR(CAT_STATEMENT_TABLE_FULL, -860000) // JMC NEW_ERROR(CAT_RESOURCE_NAME_LENGTH_EXCEEDED, -861000) NEW_ERROR(CAT_NO_CHECKSUM_FOR_REPLICA, -862000) +NEW_ERROR(CAT_NO_ROWS_UPDATED, -863000) /** @} */ /* 880,000 to 889,000 Deprecated */ diff --git a/lib/core/src/dns_cache.cpp b/lib/core/src/dns_cache.cpp index 2c8567a171..373eb01afc 100644 --- a/lib/core/src/dns_cache.cpp +++ b/lib/core/src/dns_cache.cpp @@ -218,6 +218,24 @@ namespace irods::experimental::net::dns_cache catch (...) {} } // deinit + auto init_no_create(const std::string_view _shm_name) -> void + { + g_owner_pid = 0; + + g_segment_name = std::string{_shm_name}; + g_mutex_name = g_segment_name + "_mutex"; + + g_segment = std::make_unique(bi::open_only, g_segment_name.data()); + g_allocator = std::make_unique(g_segment->get_segment_manager()); + g_mutex = std::make_unique(bi::open_only, g_mutex_name.data()); + g_map = g_segment->construct(bi::anonymous_instance)(std::less{}, *g_allocator); + } // init_no_create + + auto shared_memory_name() -> std::string_view + { + return g_segment_name; + } // shared_memory_name + auto insert_or_assign(const std::string_view _key, const addrinfo& _info, seconds _expires_after) -> bool diff --git a/lib/core/src/hostname_cache.cpp b/lib/core/src/hostname_cache.cpp index ae695a3cf5..b397063431 100644 --- a/lib/core/src/hostname_cache.cpp +++ b/lib/core/src/hostname_cache.cpp @@ -131,6 +131,24 @@ namespace irods::experimental::net::hostname_cache catch (...) {} } // deinit + auto init_no_create(const std::string_view _shm_name) -> void + { + g_owner_pid = 0; + + g_segment_name = std::string{_shm_name}; + g_mutex_name = g_segment_name + "_mutex"; + + g_segment = std::make_unique(bi::open_only, g_segment_name.data()); + g_allocator = std::make_unique(g_segment->get_segment_manager()); + g_mutex = std::make_unique(bi::open_only, g_mutex_name.data()); + g_map = g_segment->construct(bi::anonymous_instance)(std::less{}, *g_allocator); + } // init_no_create + + auto shared_memory_name() -> std::string_view + { + return g_segment_name; + } // shared_memory_name + auto insert_or_assign(const std::string_view _key, const std::string_view _alias, std::chrono::seconds _expires_after) -> bool diff --git a/lib/core/src/irods_client_negotiation.cpp b/lib/core/src/irods_client_negotiation.cpp index 0009561ebd..db9baf6871 100644 --- a/lib/core/src/irods_client_negotiation.cpp +++ b/lib/core/src/irods_client_negotiation.cpp @@ -8,6 +8,7 @@ #include "irods/irods_hasher_factory.hpp" #include "irods/irods_configuration_parser.hpp" #include "irods/MD5Strategy.hpp" +#include "irods/rcGlobalExtern.h" #include "irods/sockComm.h" #include "irods/sockCommNetworkInterface.hpp" @@ -211,28 +212,10 @@ namespace irods /// =-=-=-=-=-=-=- /// @brief function which determines if a client/server negotiation is needed /// on the server side - bool do_client_server_negotiation_for_server( ) { - // =-=-=-=-=-=-=- - // check the SP_OPTION for the string stating a negotiation is requested - char* opt_ptr = getenv( RODS_CS_NEG ); - - // =-=-=-=-=-=-=- - // if it is not set then move on - if ( !opt_ptr || strlen( opt_ptr ) == 0 ) { - return false; - } - - // =-=-=-=-=-=-=- + bool do_client_server_negotiation_for_server(const char* _neg) { // if it is set then check for our magic token which requests // the negotiation, if it is not the magic token, move on - std::string opt_str( opt_ptr ); - if ( std::string::npos == opt_str.find( REQ_SVR_NEG ) ) { - return false; - } - - // =-=-=-=-=-=-=- - // otherwise, its a go. - return true; + return _neg && std::string_view{_neg} == REQ_SVR_NEG; } // do_client_server_negotiation_for_server /// =-=-=-=-=-=-=- @@ -315,22 +298,19 @@ namespace irods return ret; } - // =-=-=-=-=-=-=- - // attempt to get the server config, if we can then we must be an - // Agent. sign the SID and send it showing that we are a trusted - // Agent and not an actual Client ( icommand, jargon connection etc ) std::string cli_msg; - try { - // If server_config cannot be read, this must be a "pure" client. An - // irods::exception will be thrown in that case. - server_properties::instance(); - + // Determine if we're operating within the server or as a pure client. + // + // If we originated from the server, as identified via ProcessType, sign the SID and send + // it showing that we are a trusted agent and not a pure client (e.g. icommands, jargon, etc). + if (AGENT_PT == ProcessType || SERVER_PT == ProcessType) { try { boost::optional zone_key; try { zone_key.reset(irods::get_server_property(irods::KW_CFG_ZONE_KEY)); - } catch (const irods::exception&) { + } + catch (const irods::exception&) { zone_key.reset(irods::get_server_property(LOCAL_ZONE_SID_KW)); } @@ -362,13 +342,15 @@ namespace irods irods::kvp_association() + signed_zone_key + irods::kvp_delimiter(); - } catch (const irods::exception& e) { + } + catch (const irods::exception& e) { irods::log(LOG_WARNING, fmt::format( "[{}:{}] - failed to retrieve and sign local zone_key [{}]", __func__, __LINE__, e.client_display_what())); return irods::error(e); } - } catch (const irods::exception& e) { + } + else { // This is a pure client, just continue. } diff --git a/lib/core/src/irods_default_paths.cpp b/lib/core/src/irods_default_paths.cpp index 89e0ea78f5..2ba6ee09fd 100644 --- a/lib/core/src/irods_default_paths.cpp +++ b/lib/core/src/irods_default_paths.cpp @@ -19,7 +19,7 @@ namespace irods fs::path get_irods_lib_directory() { Dl_info dl_info; - const int dladdr_ret = dladdr(__FUNCTION__, &dl_info); + const int dladdr_ret = dladdr(__func__, &dl_info); if (dladdr_ret == 0) { THROW(-1, "dladdr returned 0"); } @@ -90,6 +90,13 @@ namespace irods return path.lexically_normal(); } + fs::path + get_irods_sbin_directory() { + fs::path install_sbindir{IRODS_DEFAULT_PATH_SBINDIR}; + install_sbindir = install_sbindir.lexically_normal(); + return get_irods_directory_impl(install_sbindir); + } + fs::path get_irods_config_directory() { fs::path install_confdir{IRODS_DEFAULT_PATH_SYSCONFDIR}; @@ -98,6 +105,13 @@ namespace irods return get_irods_directory_impl(install_confdir); } + fs::path + get_irods_runstate_directory() { + fs::path install_runstatedir{IRODS_DEFAULT_PATH_RUNSTATEDIR}; + install_runstatedir = install_runstatedir.lexically_normal(); + return get_irods_directory_impl(install_runstatedir); + } + fs::path get_irods_home_directory() { fs::path install_homedir{IRODS_DEFAULT_PATH_HOMEDIR}; @@ -112,8 +126,18 @@ namespace irods return get_irods_directory_impl(install_plugdir); } - fs::path get_irods_stacktrace_directory() - { - return get_irods_home_directory().append("stacktraces"); + fs::path + get_irods_stacktrace_directory() { + return get_irods_home_directory() / "stacktraces"; + } + + fs::path + get_irods_proc_directory() { + return get_irods_home_directory() / "log/proc"; + } + + fs::path + get_irods_msiExecCmd_bin_directory() { + return get_irods_home_directory() / "msiExecCmd_bin"; } } // namespace irods diff --git a/lib/core/src/irods_get_full_path_for_config_file.cpp b/lib/core/src/irods_get_full_path_for_config_file.cpp index 76ecd363ab..e8fa9c2f21 100644 --- a/lib/core/src/irods_get_full_path_for_config_file.cpp +++ b/lib/core/src/irods_get_full_path_for_config_file.cpp @@ -1,22 +1,17 @@ -// =-=-=-=-=-=-=- -// boost includes -#include -#include -#include - -// =-=-=-=-=-=-=- -// stl includes -#include - -// =-=-=-=-=-=-=- -// irods includes #include "irods/rodsErrorTable.h" #include "irods/irods_log.hpp" #include "irods/irods_get_full_path_for_config_file.hpp" #include "irods/irods_default_paths.hpp" #include "irods/irods_exception.hpp" -namespace irods { +#include +#include +#include + +#include + +namespace irods +{ error get_full_path_for_config_file( const std::string& _cfg_file, std::string& _full_path ) { @@ -38,7 +33,7 @@ namespace irods { std::string msg( "config file not found [" ); msg += _cfg_file + "]"; return ERROR(SYS_INVALID_INPUT_PARAM, msg); - } + } // get_full_path_for_config_file error get_full_path_for_unmoved_configs( const std::string& _cfg_file, @@ -61,5 +56,5 @@ namespace irods { std::string msg( "config file not found [" ); msg += _cfg_file + "]"; return ERROR(SYS_INVALID_INPUT_PARAM, msg); - } -} + } // get_full_path_for_unmoved_configs +} // namespace irods diff --git a/lib/core/src/irods_logger.cpp b/lib/core/src/irods_logger.cpp index b98acd598f..97bac22403 100644 --- a/lib/core/src/irods_logger.cpp +++ b/lib/core/src/irods_logger.cpp @@ -52,10 +52,11 @@ namespace irods::experimental::log : public spdlog::sinks::base_sink { public: - test_mode_ipc_sink() - : mutex_{ipc::open_or_create, shm_name} - , file_{(irods::get_irods_home_directory() / "log" / "test_mode_output.log").string(), std::ios_base::app} - , owner_pid_{getpid()} + explicit test_mode_ipc_sink(pid_t _pid) + : shm_name_{fmt::format("irods_test_mode_ipc_sink_{}", _pid)} + , mutex_{std::make_unique(ipc::open_or_create, shm_name_.c_str())} + , file_{(irods::get_irods_home_directory() / "log/test_mode_output.log").c_str(), std::ios_base::app} + , owner_pid_{_pid} { } @@ -66,7 +67,7 @@ namespace irods::experimental::log { try { if (getpid() == owner_pid_) { - ipc::named_mutex::remove(shm_name); + ipc::named_mutex::remove(shm_name_.c_str()); } } catch (const ipc::interprocess_exception& e) { @@ -78,7 +79,7 @@ namespace irods::experimental::log void sink_it_(const spdlog::details::log_msg& msg) override { try { - ipc::scoped_lock lock{mutex_}; + ipc::scoped_lock lock{*mutex_}; file_ << msg.payload.data() << std::endl; } catch (const ipc::interprocess_exception& e) { @@ -91,9 +92,8 @@ namespace irods::experimental::log } private: - inline static const char* const shm_name = "irods_test_mode_ipc_sink"; - - ipc::named_mutex mutex_; + std::string shm_name_; + std::unique_ptr mutex_; std::ofstream file_; const pid_t owner_pid_; }; // class test_mode_ipc_sink @@ -102,9 +102,10 @@ namespace irods::experimental::log : public spdlog::sinks::base_sink { public: - stdout_ipc_sink() - : mutex_{ipc::open_or_create, shm_name} - , owner_pid_{getpid()} + explicit stdout_ipc_sink(pid_t _pid) + : shm_name_{fmt::format("irods_stdout_ipc_sink_{}", _pid)} + , mutex_{std::make_unique(ipc::open_or_create, shm_name_.c_str())} + , owner_pid_{_pid} { } @@ -115,7 +116,7 @@ namespace irods::experimental::log { try { if (getpid() == owner_pid_) { - ipc::named_mutex::remove(shm_name); + ipc::named_mutex::remove(shm_name_.c_str()); } } catch (const ipc::interprocess_exception& e) { @@ -127,7 +128,7 @@ namespace irods::experimental::log void sink_it_(const spdlog::details::log_msg& msg) override { try { - ipc::scoped_lock lock{mutex_}; + ipc::scoped_lock lock{*mutex_}; std::cout << msg.payload.data() << std::endl; } catch (const ipc::interprocess_exception& e) { @@ -140,20 +141,19 @@ namespace irods::experimental::log } private: - inline static const char* const shm_name = "irods_stdout_ipc_sink"; - - ipc::named_mutex mutex_; + std::string shm_name_; + std::unique_ptr mutex_; const pid_t owner_pid_; }; // class stdout_ipc_sink #endif // IRODS_ENABLE_SYSLOG - void init(bool _write_to_stdout, bool _enable_test_mode) noexcept + void init(pid_t _pid, bool _write_to_stdout, bool _enable_test_mode) noexcept { #ifdef IRODS_ENABLE_SYSLOG std::vector sinks; if (_write_to_stdout) { - sinks.push_back(std::make_shared()); + sinks.push_back(std::make_shared(_pid)); } else { std::string id; @@ -163,7 +163,7 @@ namespace irods::experimental::log } if (_enable_test_mode) { - sinks.push_back(std::make_shared()); + sinks.push_back(std::make_shared(_pid)); } g_log = std::make_shared("composite_logger", std::begin(sinks), std::end(sinks)); diff --git a/lib/core/src/irods_server_properties.cpp b/lib/core/src/irods_server_properties.cpp index 9dedfe446c..f16e4dc1ff 100644 --- a/lib/core/src/irods_server_properties.cpp +++ b/lib/core/src/irods_server_properties.cpp @@ -206,11 +206,6 @@ namespace irods return singleton; } // instance - server_properties::server_properties() - { - capture(); - } // ctor - void server_properties::capture() { auto lock = acquire_write_lock(); @@ -219,22 +214,33 @@ namespace irods return; } - std::string svr_fn; - irods::error ret = irods::get_full_path_for_config_file(SERVER_CONFIG_FILE, svr_fn); - if (!ret.ok()) { - THROW(ret.code(), - fmt::format("[{}:{}] - Failed to find server config file [{}]", __func__, __LINE__, ret.result())); + std::ifstream in{file_path_}; + if (!in) { + THROW(FILE_OPEN_ERR, fmt::format("[{}:{}] - Failed to open configuration file [{}]", __func__, __LINE__, file_path_)); } - std::ifstream svr{svr_fn}; + config_props_ = json::parse(in); + } // capture + + void server_properties::init(const std::string& _path) + { + auto lock = acquire_write_lock(); - if (!svr) { - THROW(FILE_OPEN_ERR, fmt::format("[{}:{}] - Failed to open server config file", __func__, __LINE__)); + file_path_ = _path; + std::ifstream in{file_path_}; + if (!in) { + THROW(FILE_OPEN_ERR, fmt::format("[{}:{}] - Failed to open configuration file [{}]", __func__, __LINE__, file_path_)); } - config_props_ = json::parse(svr); + config_props_ = json::parse(in); } // capture + void server_properties::set_configuration(nlohmann::json _config) + { + auto lock = acquire_write_lock(); + config_props_ = std::move(_config); + } // set_configuration + nlohmann::json server_properties::reload() { using json = nlohmann::json; @@ -245,20 +251,11 @@ namespace irods new_values = process_remote_configuration(capture_remote_configuration()); } else { - std::string svr_fn; - irods::error ret = irods::get_full_path_for_config_file(SERVER_CONFIG_FILE, svr_fn); - if (!ret.ok()) { - THROW( - ret.code(), - fmt::format("[{}:{}] - Failed to find server config file [{}]", __func__, __LINE__, ret.result())); - } - - std::ifstream svr{svr_fn}; - - if (!svr) { - THROW(FILE_OPEN_ERR, fmt::format("[{}:{}] - Failed to open server config file", __func__, __LINE__)); + std::ifstream in{file_path_}; + if (!in) { + THROW(FILE_OPEN_ERR, fmt::format("[{}:{}] - Failed to open configuration file [{}]", __func__, __LINE__, file_path_)); } - new_values = json::parse(svr); + new_values = json::parse(in); } auto lock = acquire_write_lock(); diff --git a/lib/core/src/rcMisc.cpp b/lib/core/src/rcMisc.cpp index d203a9f66d..6b6afd9588 100644 --- a/lib/core/src/rcMisc.cpp +++ b/lib/core/src/rcMisc.cpp @@ -38,6 +38,8 @@ #include "irods/specificQuery.h" #include "irods/ticketAdmin.h" #include "irods/plugins/api/switch_user_types.h" +#include "irods/delay_rule_tag.h" +#include "irods/delay_rule_tag_clear.h" #include #include @@ -1813,6 +1815,32 @@ void clearGenquery2Input(void* _p) std::memset(q, 0, sizeof(Genquery2Input)); } // clearGenquery2Input +void clearDelayRuleTagInput(void* _p) +{ + if (!_p) { + return; + } + + auto* q = static_cast(_p); + + clearKeyVal(&q->condInput); + + std::memset(q, 0, sizeof(DelayRuleTagInput)); +} // DelayRuleTagInput + +void clearDelayRuleTagClearInput(void* _p) +{ + if (!_p) { + return; + } + + auto* q = static_cast(_p); + + clearKeyVal(&q->condInput); + + std::memset(q, 0, sizeof(DelayRuleTagClearInput)); +} // DelayRuleTagClearInput + int parseMultiStr( char *strInput, strArray_t *strArray ) { char *startPtr, *endPtr; diff --git a/lib/core/src/sockComm.cpp b/lib/core/src/sockComm.cpp index 27fa765d8f..5d67e5471e 100644 --- a/lib/core/src/sockComm.cpp +++ b/lib/core/src/sockComm.cpp @@ -522,16 +522,16 @@ sockOpenForInConn( rsComm_t *rsComm, int *portNum, char **addr, int proto ) { /* rsAcceptConn - Server accept connection */ int -rsAcceptConn( rsComm_t *svrComm ) { - socklen_t len = sizeof( svrComm->remoteAddr ); +rsAcceptConn( rsComm_t *_comm ) { + socklen_t len = sizeof( _comm->remoteAddr ); - const int saved_socket_flags = fcntl( svrComm->sock, F_GETFL ); - int status = fcntl( svrComm->sock, F_SETFL, saved_socket_flags | O_NONBLOCK ); + const int saved_socket_flags = fcntl( _comm->sock, F_GETFL ); + int status = fcntl( _comm->sock, F_SETFL, saved_socket_flags | O_NONBLOCK ); if ( status < 0 ) { rodsLogError( LOG_NOTICE, status, "failed to set flags with nonblock on fnctl" ); } - const int newSock = accept( svrComm->sock, ( struct sockaddr * ) &svrComm->remoteAddr, &len ); - status = fcntl( svrComm->sock, F_SETFL, saved_socket_flags ); + const int newSock = accept( _comm->sock, ( struct sockaddr * ) &_comm->remoteAddr, &len ); + status = fcntl( _comm->sock, F_SETFL, saved_socket_flags ); if ( status < 0 ) { rodsLogError( LOG_NOTICE, status, "failed to revert flags on fnctl" ); } @@ -540,10 +540,10 @@ rsAcceptConn( rsComm_t *svrComm ) { const int status = SYS_SOCK_ACCEPT_ERR - errno; rodsLogError( LOG_NOTICE, status, "rsAcceptConn: accept error for socket %d", - svrComm->sock ); + _comm->sock ); return newSock; } - rodsSetSockOpt( newSock, svrComm->windowSize ); + rodsSetSockOpt( newSock, _comm->windowSize ); return newSock; } diff --git a/packaging/postinstall.sh b/packaging/postinstall.sh index a82f9db19f..19aac1d058 100755 --- a/packaging/postinstall.sh +++ b/packaging/postinstall.sh @@ -73,6 +73,7 @@ chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME $IRODS_HOME/msiExecC chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME $IRODS_HOME/msiExecCmd_bin/univMSSInterface.sh.template chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME $IRODS_HOME/msiExecCmd_bin/irodsServerMonPerf.template chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME $IRODS_HOME/msiExecCmd_bin/hello +chown $IRODS_SERVICE_ACCOUNT_NAME:$IRODS_SERVICE_GROUP_NAME /var/run/irods # TODO Confirm if this directory exists on all supported platforms. # =-=-=-=-=-=-=- # set permission on single testfile to a probably unresolvable uid diff --git a/packaging/server_config.json.template b/packaging/server_config.json.template index 79cba18a3f..2f0b248ce9 100644 --- a/packaging/server_config.json.template +++ b/packaging/server_config.json.template @@ -1,6 +1,6 @@ { "schema_name": "server_config", - "schema_version": "v4", + "schema_version": "v5", "advanced_settings": { "agent_factory_watcher_sleep_time_in_seconds": 5, "checksum_read_buffer_size_in_bytes": 1048576, @@ -37,6 +37,7 @@ "default_hash_scheme": "SHA256", "environment_variables": {}, "federation": [], + "graceful_shutdown_timeout_in_seconds": 30, "host_access_control": { "access_entries": [] }, @@ -69,20 +70,20 @@ "instance_name": "irods_rule_engine_plugin-irods_rule_language-instance", "plugin_name": "irods_rule_engine_plugin-irods_rule_language", "plugin_specific_configuration": { - "re_data_variable_mapping_set": [ - "core" - ], - "re_function_name_mapping_set": [ - "core" - ], - "re_rulebase_set": [ - "core" - ], - "regexes_for_supported_peps": [ - "ac[^ ]*", - "msi[^ ]*", - "[^ ]*pep_[^ ]*_(pre|post|except|finally)" - ] + "re_data_variable_mapping_set": [ + "core" + ], + "re_function_name_mapping_set": [ + "core" + ], + "re_rulebase_set": [ + "core" + ], + "regexes_for_supported_peps": [ + "ac[^ ]*", + "msi[^ ]*", + "[^ ]*pep_[^ ]*_(pre|post|except|finally)" + ] }, "shared_memory_instance" : "irods_rule_language_rule_engine" }, diff --git a/plugins/database/src/db_plugin.cpp b/plugins/database/src/db_plugin.cpp index 275602ab17..9c58b70851 100644 --- a/plugins/database/src/db_plugin.cpp +++ b/plugins/database/src/db_plugin.cpp @@ -15883,6 +15883,91 @@ auto db_execute_genquery2_sql(irods::plugin_context& _ctx, } } // db_execute_genquery2_sql +auto db_delay_rule_tag(irods::plugin_context& _ctx, + const char* _rule_id, + const char* _tag) -> irods::error +{ + if (const auto ret = _ctx.valid(); !ret.ok()) { + return PASS(ret); + } + + if (!_rule_id || !_tag) { + log_db::error("{}: Received one or more null pointers.", __func__); + return ERROR(SYS_INTERNAL_NULL_INPUT_ERR, "Received one or more null pointers."); + } + + try { + auto [db_instance, db_conn] = irods::experimental::catalog::new_database_connection(); + + nanodbc::statement stmt{db_conn}; + nanodbc::prepare(stmt, "update R_RULE_EXEC set exe_status = ? where rule_exec_id = ? and (exe_status is null or exe_status = '')"); + + stmt.bind(0, _tag); + stmt.bind(1, _rule_id); + + if (const auto result = nanodbc::execute(stmt); result.affected_rows() != 1) { + auto msg = fmt::format("{}: Failed to tag delay rule [rule_id={}, tag={}].", __func__, _rule_id, _tag); + log_db::error(msg); + return ERROR(CAT_NO_ROWS_UPDATED, std::move(msg)); + } + + return SUCCESS(); + } + catch (const irods::exception& e) { + log_db::error("{}: {}", __func__, e.client_display_what()); + return ERROR(e.code(), e.what()); + } + catch (const std::exception& e) { + log_db::error("{}: {}", __func__, e.what()); + return ERROR(SYS_LIBRARY_ERROR, e.what()); + } + catch (...) { + log_db::error("{}: An unknown error was caught.", __func__); + return ERROR(SYS_UNKNOWN_ERROR, "An unknown error was caught."); + } +} // db_delay_rule_tag + +auto db_delay_rule_tag_clear(irods::plugin_context& _ctx, const char* _rule_id) -> irods::error +{ + if (const auto ret = _ctx.valid(); !ret.ok()) { + return PASS(ret); + } + + if (!_rule_id) { + log_db::error("{}: Rule ID cannot be a null pointer.", __func__); + return ERROR(SYS_INTERNAL_NULL_INPUT_ERR, "Rule ID cannot be a null pointer."); + } + + try { + auto [db_instance, db_conn] = irods::experimental::catalog::new_database_connection(); + + nanodbc::statement stmt{db_conn}; + nanodbc::prepare(stmt, "update R_RULE_EXEC set exe_status = NULL where rule_exec_id = ?"); + + stmt.bind(0, _rule_id); + + if (const auto result = nanodbc::execute(stmt); result.affected_rows() != 1) { + auto msg = fmt::format("{}: Failed to clear tag attached to delay rule [rule_id={}].", __func__, _rule_id); + log_db::error(msg); + return ERROR(CAT_NO_ROWS_UPDATED, std::move(msg)); + } + + return SUCCESS(); + } + catch (const irods::exception& e) { + log_db::error("{}: {}", __func__, e.client_display_what()); + return ERROR(e.code(), e.what()); + } + catch (const std::exception& e) { + log_db::error("{}: {}", __func__, e.what()); + return ERROR(SYS_LIBRARY_ERROR, e.what()); + } + catch (...) { + log_db::error("{}: An unknown error was caught.", __func__); + return ERROR(SYS_UNKNOWN_ERROR, "An unknown error was caught."); + } +} // db_delay_rule_tag_clear + // =-=-=-=-=-=-=- // irods::error db_start_operation( irods::plugin_property_map& _props ) { @@ -16298,6 +16383,12 @@ irods::database* plugin_factory( DATABASE_OP_EXECUTE_GENQUERY2_SQL, function*, char**)>( db_execute_genquery2_sql)); + pg->add_operation( + DATABASE_OP_DELAY_RULE_TAG, + function(db_delay_rule_tag)); + pg->add_operation( + DATABASE_OP_DELAY_RULE_TAG_CLEAR, + function(db_delay_rule_tag_clear)); return pg; } // plugin_factory diff --git a/plugins/network/src/ssl.cpp b/plugins/network/src/ssl.cpp index f5ddbcac27..f82053d21c 100644 --- a/plugins/network/src/ssl.cpp +++ b/plugins/network/src/ssl.cpp @@ -9,6 +9,7 @@ #include "irods/irods_buffer_encryption.hpp" #include "irods/sockCommNetworkInterface.hpp" #include "irods/rcMisc.h" +#include "irods/irods_logger.hpp" #include #include @@ -22,6 +23,11 @@ #include +namespace +{ + using log_net = irods::experimental::log::network; +} // anonymous namespace + // =-=-=-=-=-=-=- // work around for SSL Macro version issues #if OPENSSL_VERSION_NUMBER < 0x10100000 @@ -375,56 +381,55 @@ irods::error ssl_socket_read( return ERROR(SYS_INVALID_INPUT_PARAM, "Null buffer or ssl pointer."); } - // Initialize the file descriptor set fd_set set; - FD_ZERO( &set ); - FD_SET( _socket, &set ); - - // local copy of time value? - struct timeval timeout; - if ( _time_value != NULL ) { - timeout = ( *_time_value ); - } - - // local working variables - int len_to_read = _length; - char* read_ptr = static_cast( _buffer ); - - // reset bytes read + struct timeval timeout{}; + int len_to_read = _length; + char* read_ptr = static_cast( _buffer ); _bytes_read = 0; + irods::error result = SUCCESS(); // loop while there is data to read - irods::error result = SUCCESS(); - while ( result.ok() && len_to_read > 0 ) { + while (result.ok() && len_to_read > 0) { // do a time out managed select of the socket fd - if ( SSL_pending( _ssl ) == 0 && NULL != _time_value ) { - int status = select( _socket + 1, &set, NULL, NULL, &timeout ); + if (SSL_pending(_ssl) == 0 && _time_value) { + FD_ZERO(&set); // NOLINT + FD_SET(_socket, &set); // NOLINT + timeout = *_time_value; + + log_net::debug("{}: Calling select() with timeout [{}.{}].", __func__, timeout.tv_sec, timeout.tv_usec); + int status = select(_socket + 1, &set, nullptr, nullptr, &timeout); + if ( status == 0 ) { + log_net::debug("{}: select() timed out. timeout object now holds [{}.{}].", __func__, timeout.tv_sec, timeout.tv_usec); + // the select has timed out if ( ( _length - len_to_read ) > 0 ) { - result = ERROR( _length - len_to_read, "failed to read requested number of bytes" ); + result = ERROR(_length - len_to_read, "failed to read requested number of bytes"); } else { - result = ERROR( SYS_SOCK_READ_TIMEDOUT, "socket timeout error" ); + result = ERROR(SYS_SOCK_READ_TIMEDOUT, "socket timeout error"); } - } else if ( status < 0 ) { // keep trying on interrupt or just error out - int err_status = SYS_SOCK_READ_ERR - errno; - if (errno != EINTR) { - return ERROR(err_status, "Error on select."); + if (EINTR == errno) { + // TODO Need a way to detect that SIGUSR1 signal was received. + // Perhaps we read the value of g_terminate or simply return and handle + // termination at an earlier place in the callstack. + return ERROR(INTERRUPT_DETECTED, fmt::format("{} interrupted by signal", __func__)); } + + int err_status = SYS_SOCK_READ_ERR - errno; + return ERROR(err_status, "Error on select."); } // else } // if tv // select has been done, finally do the read - int num_bytes = SSL_read( _ssl, ( void * ) read_ptr, len_to_read ); + log_net::debug("{}: Reading [{}] bytes from socket [{}].", __func__, len_to_read, _socket); + int num_bytes = SSL_read(_ssl, (void*) read_ptr, len_to_read); - // =-=-=-=-=-=-=- // error trapping the read if ( SSL_get_error( _ssl, num_bytes ) != SSL_ERROR_NONE ) { - // =-=-=-=-=-=-=- // gracefully handle an interrupt if ( EINTR == errno ) { errno = 0; @@ -437,7 +442,7 @@ irods::error ssl_socket_read( // all has gone well, do byte book keeping len_to_read -= num_bytes; - read_ptr += num_bytes; + read_ptr += num_bytes; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) _bytes_read += num_bytes; } // while diff --git a/plugins/network/src/tcp.cpp b/plugins/network/src/tcp.cpp index 5b0f19a948..f3726ce3d6 100644 --- a/plugins/network/src/tcp.cpp +++ b/plugins/network/src/tcp.cpp @@ -1,6 +1,8 @@ +#include "irods/irods_at_scope_exit.hpp" #include "irods/rodsDef.h" #include "irods/msParam.h" #include "irods/rcConnect.h" +#include "irods/rodsErrorTable.h" #include "irods/sockComm.h" #include "irods/irods_network_plugin.hpp" #include "irods/irods_network_constants.hpp" @@ -8,12 +10,20 @@ #include "irods/irods_stacktrace.hpp" #include "irods/sockCommNetworkInterface.hpp" #include "irods/rcMisc.h" +#include "irods/irods_logger.hpp" + +#include #include #include #include #include +namespace +{ + using log_net = irods::experimental::log::network; +} // anonymous namespace + // =-=-=-=-=-=-=- // local function to read a buffer from a socket irods::error tcp_socket_read( @@ -21,57 +31,65 @@ irods::error tcp_socket_read( void* _buffer, int _length, int& _bytes_read, - struct timeval* _time_value ) { - // =-=-=-=-=-=-=- - // Initialize the file descriptor set - fd_set set; - FD_ZERO( &set ); - FD_SET( _socket, &set ); + struct timeval* _time_value ) +{ + log_net::debug("{}: BEGIN", __func__); - // =-=-=-=-=-=-=- - // local copy of time value? - struct timeval timeout; - if ( _time_value != NULL ) { - timeout = ( *_time_value ); - } - - // =-=-=-=-=-=-=- - // local working variables - int len_to_read = _length; - char* read_ptr = static_cast( _buffer ); + // NOLINTNEXTLINE(bugprone-lambda-function-name) + irods::at_scope_exit log_exit{[fn = __func__] { log_net::debug("{}: END", fn); }}; - // =-=-=-=-=-=-=- - // reset bytes read + fd_set set; + struct timeval timeout{}; + int len_to_read = _length; + char* read_ptr = static_cast( _buffer ); _bytes_read = 0; while ( len_to_read > 0 ) { if ( nullptr != _time_value ) { - const int status = select( _socket + 1, &set, NULL, NULL, &timeout ); + // Must always reset the fd_set and timeout before a call to select(). + FD_ZERO(&set); // NOLINT + FD_SET(_socket, &set); // NOLINT + timeout = *_time_value; + + log_net::debug("{}: Calling select() with timeout [{}.{}].", __func__, timeout.tv_sec, timeout.tv_usec); + const int status = select( _socket + 1, &set, nullptr, nullptr, &timeout ); + if ( status == 0 ) { // the select has timed out - return ERROR( SYS_SOCK_READ_TIMEDOUT, boost::format("socket timeout with [%d] bytes read") % _bytes_read); - } else if ( status < 0 ) { + log_net::debug("{}: select() timed out. timeout object now holds [{}.{}].", __func__, timeout.tv_sec, timeout.tv_usec); + return ERROR( SYS_SOCK_READ_TIMEDOUT, fmt::format("socket timeout with [{}] bytes read", _bytes_read)); + } + + if ( status < 0 ) { + log_net::debug("{}: select() encountered an error [{}], errno = [{}].", __func__, status, errno); + if ( errno == EINTR ) { - continue; - } else { - return ERROR( SYS_SOCK_READ_ERR - errno, boost::format("error on select after [%d] bytes read") % _bytes_read); + // TODO Need a way to detect that SIGUSR1 signal was received. + // Perhaps we read the value of g_terminate or simply return and handle + // termination at an earlier place in the callstack. + return ERROR(INTERRUPT_DETECTED, fmt::format("{} interrupted by signal", __func__)); } + + return ERROR( SYS_SOCK_READ_ERR - errno, fmt::format("error on select after [{}] bytes read", _bytes_read)); } // else } // if tv - int num_bytes = read( _socket, ( void * ) read_ptr, len_to_read ); + log_net::debug("{}: Reading [{}] bytes from socket [{}].", __func__, len_to_read, _socket); + int num_bytes = read(_socket, static_cast(read_ptr), len_to_read); if ( num_bytes < 0 ) { + log_net::debug("{}: read() encountered an error [{}], errno = [{}].", __func__, num_bytes, errno); if ( EINTR == errno ) { errno = 0; num_bytes = 0; } else { - return ERROR(SYS_SOCK_READ_ERR - errno, boost::format("error reading from socket after [%d] bytes read") % _bytes_read); + return ERROR(SYS_SOCK_READ_ERR - errno, fmt::format("error reading from socket after [{}] bytes read", _bytes_read)); } } else if ( num_bytes == 0 ) { + log_net::debug("{}: read() returned 0. Peer must have disconnected.", __func__); break; } len_to_read -= num_bytes; - read_ptr += num_bytes; + read_ptr += num_bytes; // NOLINT(cppcoreguidelines-pro-bounds-pointer-arithmetic) _bytes_read += num_bytes; } // while @@ -160,6 +178,7 @@ irods::error tcp_read_msg_header( irods::plugin_context& _ctx, void* _buffer, struct timeval* _time_val ) { + log_net::debug("{}: BEGIN", __func__); // =-=-=-=-=-=-=- // check the context irods::error ret = _ctx.valid< irods::tcp_object >(); @@ -430,6 +449,7 @@ irods::error read_bytes_buf( bytesBuf_t* _buffer, irodsProt_t _protocol, struct timeval* _time_val ) { + log_net::debug("{}: BEGIN", __func__); // =-=-=-=-=-=-=- // trap input buffer ptr if ( !_buffer || !_buffer->buf ) { diff --git a/plugins/rule_engines/irods_rule_language/src/configuration.cpp b/plugins/rule_engines/irods_rule_language/src/configuration.cpp index 7f8d0ea0a3..9c6b832d84 100644 --- a/plugins/rule_engines/irods_rule_language/src/configuration.cpp +++ b/plugins/rule_engines/irods_rule_language/src/configuration.cpp @@ -361,8 +361,6 @@ std::vector parse_irbSet(const std::string &irbSet) { return irbs; } -#define HASH_BUF_SZ (1024*1024) - int hash_rules(const std::vector &irbs, const int pid, std::string &digest) { int status; irods::Hasher hasher; @@ -397,6 +395,7 @@ int hash_rules(const std::vector &irbs, const int pid, std::string } std::string buffer_read; + constexpr auto HASH_BUF_SZ = 1024 * 1024; buffer_read.resize( HASH_BUF_SZ ); while ( in_file.read( &buffer_read[0], HASH_BUF_SZ ) ) { diff --git a/plugins/rule_engines/irods_rule_language/src/irods_rule_language.cpp b/plugins/rule_engines/irods_rule_language/src/irods_rule_language.cpp index e7896c2598..f90691238e 100644 --- a/plugins/rule_engines/irods_rule_language/src/irods_rule_language.cpp +++ b/plugins/rule_engines/irods_rule_language/src/irods_rule_language.cpp @@ -119,7 +119,7 @@ void initialize_microservice_table() extern Cache ruleEngineConfig; -irods::error start(irods::default_re_ctx&, const std::string& _instance_name) +irods::error setup(irods::default_re_ctx&, const std::string& _instance_name) { local_instance_name = _instance_name; @@ -178,11 +178,7 @@ irods::error start(irods::default_re_ctx&, const std::string& _instance_name) logger::rule_engine::error(msg); return ERROR(SYS_INVALID_INPUT_PARAM, msg); -} // start - -irods::error stop(irods::default_re_ctx& _u, const std::string& _instance_name) { - return SUCCESS(); -} +} // setup irods::error rule_exists(irods::default_re_ctx&, const std::string& _rn, bool& _ret) { if(ruleEngineConfig.ruleEngineStatus == UNINITIALIZED) { @@ -419,13 +415,19 @@ irods::error exec_rule_expression( extern "C" irods::pluggable_rule_engine* plugin_factory( const std::string& _inst_name, - const std::string& _context ) { - irods::pluggable_rule_engine* re = new irods::pluggable_rule_engine( _inst_name , _context); - re->add_operation( "start", - std::function( start ) ); + const std::string& _context ) +{ + const auto no_op = [](irods::default_re_ctx&, const std::string&) -> irods::error { + return SUCCESS(); + }; + + auto* re = new irods::pluggable_rule_engine( _inst_name , _context); + + re->add_operation("setup", std::function{setup}); + re->add_operation("teardown", std::function{no_op}); - re->add_operation( "stop", - std::function( stop ) ); + re->add_operation("start", std::function{no_op}); + re->add_operation("stop", std::function{no_op}); re->add_operation( "rule_exists", std::function( rule_exists ) ); diff --git a/plugins/rule_engines/src/cpp_default_policy.cpp b/plugins/rule_engines/src/cpp_default_policy.cpp index ba004efa22..b63f4dfc26 100644 --- a/plugins/rule_engines/src/cpp_default_policy.cpp +++ b/plugins/rule_engines/src/cpp_default_policy.cpp @@ -1171,6 +1171,14 @@ extern "C" irods::pluggable_rule_engine* plugin_factory( const std::string& _inst_name, const std::string& _context ) { irods::pluggable_rule_engine* re = new irods::pluggable_rule_engine( _inst_name , _context); + + const auto no_op = [](irods::default_re_ctx&, const std::string&) -> irods::error { + return SUCCESS(); + }; + + re->add_operation("setup", std::function{no_op}); + re->add_operation("teardown", std::function{no_op}); + re->add_operation( "start", std::function( start ) ); diff --git a/plugins/rule_engines/src/passthrough.cpp b/plugins/rule_engines/src/passthrough.cpp index cb9838c63d..d4cfa5a175 100644 --- a/plugins/rule_engines/src/passthrough.cpp +++ b/plugins/rule_engines/src/passthrough.cpp @@ -33,49 +33,24 @@ namespace int code; }; + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) std::unordered_map> pep_configs; + + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) int matched_code = 0; template using operation = std::function; - irods::error start(irods::default_re_ctx&, const std::string& _instance_name) + irods::error setup(irods::default_re_ctx&, const std::string& _instance_name) { - std::string config_path; - - if (auto error = irods::get_full_path_for_config_file("server_config.json", config_path); - !error.ok()) - { - std::string msg = "Server configuration not found [path => "; - msg += config_path; - msg += ']'; - - // clang-format off - log::rule_engine::error({{"rule_engine_plugin", "passthrough"}, - {"rule_engine_plugin_function", __func__}, - {"log_message", msg}}); - // clang-format on - - return ERROR(SYS_CONFIG_FILE_ERR, msg.c_str()); - } - - // clang-format off - log::rule_engine::trace({{"rule_engine_plugin", "passthrough"}, - {"rule_engine_plugin_function", __func__}, - {"log_message", "Reading plugin configuration ..."}}); - // clang-format on - - nlohmann::json config; - - { - std::ifstream config_file{config_path}; - config_file >> config; - } - try { + const auto config_handle = irods::server_properties::server_properties::instance().map(); + const auto& config = config_handle.get_json(); + // Iterate over the list of rule engine plugins until the Passthrough REP is found. for (const auto& re : config.at(irods::KW_CFG_PLUGIN_CONFIGURATION).at(irods::KW_CFG_PLUGIN_TYPE_RULE_ENGINE)) { - if (_instance_name == re.at(irods::KW_CFG_INSTANCE_NAME).get()) { + if (_instance_name == re.at(irods::KW_CFG_INSTANCE_NAME).get_ref()) { // Fill the "pep_configs" plugin variable with objects containing the values // defined in the "return_codes_for_peps" configuration. Each object in the list // will contain a regular expression and a code. @@ -98,10 +73,10 @@ namespace } return ERROR(SYS_CONFIG_FILE_ERR, "[passthrough] Bad rule engine plugin configuration"); - } + } // setup irods::error rule_exists(const std::string& _instance_name, - irods::default_re_ctx&, + [[maybe_unused]] irods::default_re_ctx& _ctx, const std::string& _rule_name, bool& _exists) { @@ -123,17 +98,18 @@ namespace return SUCCESS(); } - irods::error list_rules(irods::default_re_ctx&, std::vector& _rules) + irods::error list_rules(irods::default_re_ctx&, [[maybe_unused]] std::vector& _rules) { log::rule_engine::trace({{"rule_engine_plugin", "passthrough"}, {"rule_engine_plugin_function", __func__}}); return SUCCESS(); } irods::error exec_rule(const std::string& _instance_name, - irods::default_re_ctx&, + [[maybe_unused]] irods::default_re_ctx& _ctx, const std::string& _rule_name, - std::list& _rule_arguments, - irods::callback _effect_handler) + [[maybe_unused]] std::list& _rule_arguments, + // NOLINTNEXTLINE(performance-unnecessary-value-param) + [[maybe_unused]] irods::callback _effect_handler) { std::string msg = "Returned '"; msg += std::to_string(matched_code); @@ -185,12 +161,16 @@ pluggable_rule_engine* plugin_factory(const std::string& _instance_name, std::list& _rule_arguments, irods::callback _effect_handler) { + // NOLINTNEXTLINE(performance-unnecessary-value-param) return exec_rule(_instance_name, _ctx, _rule_name, _rule_arguments, _effect_handler); }; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) auto* re = new pluggable_rule_engine{_instance_name, _context}; - re->add_operation("start", operation{start}); + re->add_operation("setup", operation{setup}); + re->add_operation("teardown", operation{no_op}); + re->add_operation("start", operation{no_op}); re->add_operation("stop", operation{no_op}); re->add_operation("rule_exists", operation{rule_exists_wrapper}); re->add_operation("list_rules", operation&>{list_rules}); diff --git a/schemas/README.md b/schemas/README.md deleted file mode 100644 index 6b1260ecaf..0000000000 --- a/schemas/README.md +++ /dev/null @@ -1,12 +0,0 @@ -## Schemas - -This directory contains various schema definition files. - -**NOTE:** All schema files existing in the same directory must share the same schema type. - -The schema types for each directory are listed below. - -| Directory | Schema Type | URL | -|---|---|---| -| configuration/v4 | JSON Schema | https://json-schema.org | -| messaging/v1 | Avro | https://avro.apache.org/docs/1.11.0/spec.html | diff --git a/schemas/configuration/v4/grid_status.json.in b/schemas/configuration/v4/grid_status.json.in deleted file mode 100644 index c050fcc77e..0000000000 --- a/schemas/configuration/v4/grid_status.json.in +++ /dev/null @@ -1,14 +0,0 @@ -{ - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/grid_status.json", - "$schema": "https://json-schema.org/draft/2020-12/schema", - "type": "object", - "properties": { - "hosts": { - "type": "array", - "items": { - "$ref": "server_status.json" - } - } - }, - "required": ["hosts"] -} diff --git a/schemas/configuration/v4/client_environment.json.in b/schemas/configuration/v5/client_environment.json.in similarity index 76% rename from schemas/configuration/v4/client_environment.json.in rename to schemas/configuration/v5/client_environment.json.in index 23c50b29d2..8a0412ac2e 100644 --- a/schemas/configuration/v4/client_environment.json.in +++ b/schemas/configuration/v5/client_environment.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/client_environment.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/client_environment.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/client_hints.json.in b/schemas/configuration/v5/client_hints.json.in similarity index 82% rename from schemas/configuration/v4/client_hints.json.in rename to schemas/configuration/v5/client_hints.json.in index fc6eca8aa5..ea2debdb23 100644 --- a/schemas/configuration/v4/client_hints.json.in +++ b/schemas/configuration/v5/client_hints.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/client_hints.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/client_hints.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/configuration_directory.json.in b/schemas/configuration/v5/configuration_directory.json.in similarity index 82% rename from schemas/configuration/v4/configuration_directory.json.in rename to schemas/configuration/v5/configuration_directory.json.in index 03eed42ef8..9e1debbe58 100644 --- a/schemas/configuration/v4/configuration_directory.json.in +++ b/schemas/configuration/v5/configuration_directory.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/configuration_directory.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/configuration_directory.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/database_config.json.in b/schemas/configuration/v5/database_config.json.in similarity index 83% rename from schemas/configuration/v4/database_config.json.in rename to schemas/configuration/v5/database_config.json.in index 0c367a46df..0199c08f8c 100644 --- a/schemas/configuration/v4/database_config.json.in +++ b/schemas/configuration/v5/database_config.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/database_config.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/database_config.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/host_access_control.json.in b/schemas/configuration/v5/host_access_control.json.in similarity index 82% rename from schemas/configuration/v4/host_access_control.json.in rename to schemas/configuration/v5/host_access_control.json.in index a45ab72c34..389d6bda16 100644 --- a/schemas/configuration/v4/host_access_control.json.in +++ b/schemas/configuration/v5/host_access_control.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/host_access_control.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/host_access_control.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/host_resolution.json.in b/schemas/configuration/v5/host_resolution.json.in similarity index 86% rename from schemas/configuration/v4/host_resolution.json.in rename to schemas/configuration/v5/host_resolution.json.in index 210786bb34..be5649b62e 100644 --- a/schemas/configuration/v4/host_resolution.json.in +++ b/schemas/configuration/v5/host_resolution.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/host_resolution.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/host_resolution.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/plugin.json.in b/schemas/configuration/v5/plugin.json.in similarity index 76% rename from schemas/configuration/v4/plugin.json.in rename to schemas/configuration/v5/plugin.json.in index a28f87bc8d..a2ac8e0672 100644 --- a/schemas/configuration/v4/plugin.json.in +++ b/schemas/configuration/v5/plugin.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/plugin.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/plugin.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/resource.json.in b/schemas/configuration/v5/resource.json.in similarity index 88% rename from schemas/configuration/v4/resource.json.in rename to schemas/configuration/v5/resource.json.in index 5849b710d9..4101bf5f29 100644 --- a/schemas/configuration/v4/resource.json.in +++ b/schemas/configuration/v5/resource.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/resource.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/resource.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/rule_engine.json.in b/schemas/configuration/v5/rule_engine.json.in similarity index 80% rename from schemas/configuration/v4/rule_engine.json.in rename to schemas/configuration/v5/rule_engine.json.in index 599e822132..9300f874d2 100644 --- a/schemas/configuration/v4/rule_engine.json.in +++ b/schemas/configuration/v5/rule_engine.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/rule_engine.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/rule_engine.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/server.json.in b/schemas/configuration/v5/server.json.in similarity index 92% rename from schemas/configuration/v4/server.json.in rename to schemas/configuration/v5/server.json.in index 7779afc67d..183f719617 100644 --- a/schemas/configuration/v4/server.json.in +++ b/schemas/configuration/v5/server.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/server.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/server.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/server_config.json.in b/schemas/configuration/v5/server_config.json.in similarity index 94% rename from schemas/configuration/v4/server_config.json.in rename to schemas/configuration/v5/server_config.json.in index 54fd0d1058..a5d026baaf 100644 --- a/schemas/configuration/v4/server_config.json.in +++ b/schemas/configuration/v5/server_config.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/server_config.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/server_config.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { @@ -98,6 +98,10 @@ "required": ["catalog_provider_hosts","negotiation_key","zone_key","zone_name"] } }, + "graceful_shutdown_timeout_in_seconds": { + "type": "integer", + "minimum": 0 + }, "host_access_control": { "$ref": "host_access_control.json" }, @@ -134,7 +138,14 @@ }, "database": { "type": "object", - "additionalProperties": {"$ref": "database_config.json"}, + "propertyNames": { + "pattern": "^[^\\s]+$" + }, + "patternProperties": { + "^[^\\s]+$": { + "$ref": "database_config.json" + } + }, "maxProperties" : 1, "minProperties" : 1 }, @@ -183,6 +194,7 @@ "default_hash_scheme", "environment_variables", "federation", + "graceful_shutdown_timeout_in_seconds", "host_access_control", "host_resolution", "log_level", diff --git a/schemas/configuration/v4/server_status.json.in b/schemas/configuration/v5/server_status.json.in similarity index 85% rename from schemas/configuration/v4/server_status.json.in rename to schemas/configuration/v5/server_status.json.in index 584520cc36..3efc48008c 100644 --- a/schemas/configuration/v4/server_status.json.in +++ b/schemas/configuration/v5/server_status.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/server_status.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/server_status.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/service_account_environment.json.in b/schemas/configuration/v5/service_account_environment.json.in similarity index 96% rename from schemas/configuration/v4/service_account_environment.json.in rename to schemas/configuration/v5/service_account_environment.json.in index 22c9f5240a..975bf37082 100644 --- a/schemas/configuration/v4/service_account_environment.json.in +++ b/schemas/configuration/v5/service_account_environment.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/service_account_environment.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/service_account_environment.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "allOf": [ diff --git a/schemas/configuration/v4/unattended_installation.json.in b/schemas/configuration/v5/unattended_installation.json.in similarity index 87% rename from schemas/configuration/v4/unattended_installation.json.in rename to schemas/configuration/v5/unattended_installation.json.in index e1d5bf1f69..7e3fb97cb0 100644 --- a/schemas/configuration/v4/unattended_installation.json.in +++ b/schemas/configuration/v5/unattended_installation.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/unattended_installation.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/unattended_installation.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/version.json.in b/schemas/configuration/v5/version.json.in similarity index 93% rename from schemas/configuration/v4/version.json.in rename to schemas/configuration/v5/version.json.in index 56be26d642..fbe669c7fc 100644 --- a/schemas/configuration/v4/version.json.in +++ b/schemas/configuration/v5/version.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/version.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/version.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "properties": { diff --git a/schemas/configuration/v4/zone_bundle.json.in b/schemas/configuration/v5/zone_bundle.json.in similarity index 88% rename from schemas/configuration/v4/zone_bundle.json.in rename to schemas/configuration/v5/zone_bundle.json.in index c18fc2f104..e776e00002 100644 --- a/schemas/configuration/v4/zone_bundle.json.in +++ b/schemas/configuration/v5/zone_bundle.json.in @@ -1,5 +1,5 @@ { - "$id": "file:///@IRODS_CONFIGURATION_SCHEMA_PREFIX@@IRODS_HOME_DIRECTORY@/configuration_schemas/v4/zone_bundle.json", + "$id": "https://irods.org/configuration_schemas/v@IRODS_CONFIGURATION_SCHEMA_VERSION@/zone_bundle.json", "$schema": "https://json-schema.org/draft/2020-12/schema", "type": "object", "description": "Schema for an iRODS zone bundle (possibly featuring Federation)", diff --git a/schemas/messaging/v1/server_control_plane_command.json b/schemas/messaging/v1/server_control_plane_command.json deleted file mode 100644 index da12698038..0000000000 --- a/schemas/messaging/v1/server_control_plane_command.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "type": "record", - "name": "control_plane_command", - "fields" : [ - {"name": "command", "type": "string"}, - {"name": "options", "type": { "type": "map", "values": "string" } } - ] -} diff --git a/scripts/core_tests_list.json b/scripts/core_tests_list.json index 3bf311b3db..77196b2f37 100644 --- a/scripts/core_tests_list.json +++ b/scripts/core_tests_list.json @@ -8,7 +8,6 @@ "test_catalog", "test_collection_mtime", "test_configuration_reload", - "test_control_plane", "test_delay_queue.Test_Delay_Queue", "test_delay_queue.Test_Execution_Frequency", "test_dynamic_peps", diff --git a/scripts/irods/configuration.py b/scripts/irods/configuration.py index bb6abd0e14..988de590b4 100644 --- a/scripts/irods/configuration.py +++ b/scripts/irods/configuration.py @@ -14,7 +14,6 @@ from .exceptions import IrodsError, IrodsWarning, IrodsSchemaError from . import lib -from . import json_validation from .password_obfuscation import encode, decode from . import paths @@ -190,25 +189,6 @@ def injected_environment(self, value): self._injected_environment = lib.callback_on_change_dict(self.clear_cache, value if value is not None else {}) self.clear_cache() - @property - def schema_uri_prefix(self): - if self._schema_uri_prefix is None: - l = logging.getLogger(__name__) - l.debug('Attempting to construct schema URI...') - - key = 'schema_validation_base_uri' - self.throw_if_property_is_not_defined_in_server_config(key) - base_uri = self.server_config[key] - - key = 'schema_version' - self.throw_if_property_is_not_defined_in_server_config(key) - schema_version = self.server_config[key] - - self._schema_uri_prefix = '/'.join([base_uri, schema_version]) - l.debug('Successfully constructed schema URI.') - - return self._schema_uri_prefix - @property def admin_password(self): if not os.path.exists(os.path.dirname(paths.password_file_path())): @@ -227,71 +207,6 @@ def admin_password(self, value): print(encode(value, mtime=mtime), end='', file=f) os.utime(paths.password_file_path(), (mtime, mtime)) - def throw_if_property_is_not_defined_in_server_config(self, property_name): - if property_name not in self.server_config: - raise IrodsSchemaError('Cannot validate configuration files. [{}] is missing a required property: [{}].' - .format(paths.server_config_path(), property_name)) - - def validate_configuration(self): - l = logging.getLogger(__name__) - - key = 'schema_validation_base_uri' - self.throw_if_property_is_not_defined_in_server_config(key) - - if self.server_config[key] == 'off': - l.warn(('Schema validation is disabled; json files will not be validated against schemas. ' - 'To re-enable schema validation, supply a URI to a set of iRODS schemas in the field ' - '"schema_validation_base_uri" and a valid version in the field "schema_version" in the ' - 'server configuration file (located in %s).'), paths.server_config_path()) - return - - configuration_schema_mapping = { - 'server_config': { - 'dict': self.server_config, - 'path': paths.server_config_path() - }, - 'version': { - 'dict': self.version, - 'path': paths.version_path() - }, - 'service_account_environment': { - 'dict': self.client_environment, - 'path': self.client_environment_path - } - } - - skipped = [] - - # schema_uri_suffix is the key of a single element within configuration_schema_mapping (e.g. 'server_config'). - # config_file is the dict mapped to the key (e.g. configuration_schema_mapping['server_config']). - for schema_uri_suffix, config_file in configuration_schema_mapping.items(): - try: - schema_uri = '%s/%s.json' % ( - self.schema_uri_prefix, - schema_uri_suffix) - except IrodsError as e: - l.debug('Failed to construct schema URI') - raise IrodsWarning('%s\n%s' % ( - 'Preflight Check problem:', - lib.indent('JSON Configuration Validation failed.'))) from e - - l.debug('Attempting to validate %s against %s', config_file['path'], schema_uri) - try: - json_validation.validate_dict( - config_file['dict'], - schema_uri, - config_file['path']) - except IrodsWarning as e: - l.warning(e) - l.warning('Warning encountered in json_validation for %s, skipping validation...', - config_file['path']) - l.debug('Exception:', exc_info=True) - skipped.append(config_file['path']) - if skipped: - raise IrodsWarning('%s\n%s' % ( - 'Skipped validation for the following files:', - lib.indent(*skipped))) - def commit(self, config_dict, path, clear_cache=True, make_backup=False): l = logging.getLogger(__name__) l.info('Updating %s...', path) @@ -313,7 +228,6 @@ def clear_cache(self): self._hosts_config = None self._host_access_control_config = None self._client_environment = None - self._schema_uri_prefix = None self._execution_environment = None #provide accessors for all the paths @@ -401,14 +315,20 @@ def server_bin_directory(self): def server_executable(self): return paths.server_executable() + @property + def agent_executable(self): + return paths.agent_executable() + @property def rule_engine_executable(self): return paths.rule_engine_executable() + # TODO Does this even exist? @property def database_schema_update_directory(self): return paths.database_schema_update_directory() + # TODO Does this even exist? @property def service_account_file_path(self): return paths.service_account_file_path() diff --git a/scripts/irods/controller.py b/scripts/irods/controller.py index f177764be7..62ad76ef4a 100644 --- a/scripts/irods/controller.py +++ b/scripts/irods/controller.py @@ -7,6 +7,7 @@ import logging import os import shutil +import signal import socket import subprocess import sys @@ -37,11 +38,29 @@ def check_config(self): def server_binaries(self): return [ self.config.server_executable, + self.config.agent_executable, self.config.rule_engine_executable ] + def get_server_pid(self): + try: + # Use of this python script assumes the PID file is located in /var/run/irods. + pid_file = os.path.join(paths.runstate_directory(), 'irods', 'irods-server.pid') + if os.path.exists(pid_file): + with open(pid_file, 'r') as f: + pid = int(f.readline().strip()) + # If the user executing this python script does not have permission to send + # signals to the PID, an OSError exception will be raised indicating why. + os.kill(pid, 0) + return pid + + except (ProcessLookupError, PermissionError): + return None + def get_server_proc(self): - server_pid = lib.get_server_pid() + server_pid = self.get_server_pid() + if server_pid is None: + return None # lib.get_server_pid() does not have access to self.config, so cannot # check the pid from the pidfile against self.config.server_executable, @@ -57,184 +76,76 @@ def get_server_proc(self): return None - def start(self, write_to_stdout=False, test_mode=False): + def upgrade(self): l = logging.getLogger(__name__) - l.debug('Calling start on IrodsController') + l.debug('Calling upgrade on IrodsController') if upgrade_configuration.requires_upgrade(self.config): upgrade_configuration.upgrade(self.config) - try: - self.config.validate_configuration() - except IrodsWarning: - l.warn('Warning encountered in validation:', exc_info=True) - - if self.get_server_proc(): - raise IrodsError('iRODS already running') - - delete_s3_shmem() - - self.config.clear_cache() - if not os.path.exists(self.config.server_executable): - raise IrodsError( - 'Configuration problem:\n' - '\tThe \'%s\' application could not be found.' % ( - os.path.basename(self.config.server_executable))) - - try: - (test_file_handle, test_file_name) = tempfile.mkstemp( - dir=self.config.log_directory) - os.close(test_file_handle) - os.unlink(test_file_name) - except (IOError, OSError) as e: - raise IrodsError( - 'Configuration problem:\n' - 'The server log directory, \'%s\'' - 'is not writeable.' % ( - self.config.log_directory)) from e - - for f in ['core.re', 'core.dvm', 'core.fnm']: - path = os.path.join(self.config.config_directory, f) - if not os.path.exists(path): - shutil.copyfile(paths.get_template_filepath(path), path) - - try: - irods_port = int(self.config.server_config['zone_port']) - l.debug('Attempting to bind socket %s', irods_port) - with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: - try: - s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) - s.bind(('127.0.0.1', irods_port)) - except socket.error as e: - raise IrodsError('Could not bind port {0}.'.format(irods_port)) from e - l.debug('Socket %s bound and released successfully.', irods_port) + if self.config.is_provider: + from . import database_interface + database_interface.run_catalog_update(self.config) - if self.config.is_catalog: - from . import database_interface - database_interface.server_launch_hook(self.config) - - cmd = [self.config.server_executable] - - if write_to_stdout: - l.info('Starting iRODS server in foreground ...') + def start(self, write_to_stdout=False, test_mode=False): + l = logging.getLogger(__name__) + l.debug('Calling start on IrodsController') - cmd.append('-u') + cmd = [self.config.server_executable] - env_var_name = 'IRODS_ENABLE_TEST_MODE' - if test_mode or (env_var_name in os.environ and os.environ[env_var_name] == '1'): - cmd.append('-t') + env_var_name = 'IRODS_ENABLE_TEST_MODE' + if test_mode or (env_var_name in os.environ and os.environ[env_var_name] == '1'): + cmd.append('-t') - lib.execute_command(cmd, - stdout=sys.stdout, - stderr=sys.stdout, - cwd=self.config.server_bin_directory, - env=self.config.execution_environment) - else: - l.info('Starting iRODS server ...') - - env_var_name = 'IRODS_ENABLE_TEST_MODE' - if test_mode or (env_var_name in os.environ and os.environ[env_var_name] == '1'): - cmd.append('-t') - - lib.execute_command(cmd, - cwd=self.config.server_bin_directory, - env=self.config.execution_environment) - - try_count = 1 - max_retries = 100 - while True: - l.debug('Attempting to connect to iRODS server on port %s. Attempt #%s', - irods_port, try_count) - with contextlib.closing(socket.socket( - socket.AF_INET, socket.SOCK_STREAM)) as s: - if s.connect_ex(('127.0.0.1', irods_port)) == 0: - l.debug('Successfully connected to port %s.', irods_port) - if self.get_server_proc is None: - raise IrodsError('iRODS port is bound, but server is not started.') - s.send(b'\x00\x00\x00\x33HEARTBEAT') - message = s.recv(256) - if message != b'HEARTBEAT': - raise IrodsError('iRODS port returned non-heartbeat message:\n{0}'.format(message)) - break - if try_count >= max_retries: - raise IrodsError('iRODS server failed to start.') - try_count += 1 - time.sleep(1) - - l.info('Success') - - except IrodsError as e: - l.info('Failure') - raise e - - def irods_graceful_shutdown(self, server_proc, server_descendants, timeout=20): - start_time = time.time() - server_proc.terminate() - while time.time() < start_time + timeout: - if capture_process_tree(server_proc, server_descendants, self.server_binaries): - time.sleep(0.3) - else: - break + if write_to_stdout: + l.info('Starting iRODS server ...') + cmd.append('--stdout') + lib.execute_command(cmd, + stdout=sys.stdout, + stderr=sys.stdout, + cwd=self.config.server_bin_directory, + env=self.config.execution_environment) else: - capture_process_tree(server_proc, server_descendants, self.server_binaries) - if server_proc.is_running(): - raise IrodsError('The iRODS server did not stop within {0} seconds of ' - 'receiving SIGTERM.'.format(timeout)) - elif server_descendants: - raise IrodsError('iRODS server shut down but left behind child processes') - - def stop(self, timeout=20): + l.info('Starting iRODS server as a daemon ...') + cmd.append('-d') + lib.execute_command(cmd, + cwd=self.config.server_bin_directory, + env=self.config.execution_environment) + + def stop(self, timeout=20, graceful=False): l = logging.getLogger(__name__) self.config.clear_cache() l.debug('Calling stop on IrodsController') + + server_pid = self.get_server_pid() + if server_pid is None: + l.info('iRODS server is not running') + return + l.info('Stopping iRODS server...') - try: - server_proc = self.get_server_proc() - server_descendants = set() - if server_proc is None: - l.warning('Server pidfile missing or stale. No iRODS server running?') - else: - try: - self.irods_graceful_shutdown(server_proc, server_descendants, timeout=timeout) - except Exception as e: - l.error('Error encountered in graceful shutdown.') - l.debug('Exception:', exc_info=True) - - # deal with lingering processes - binary_to_procs_dict = self.get_binary_to_procs_dict(server_proc, server_descendants) - if binary_to_procs_dict: - l.warning('iRODS server processes remain after attempted graceful shutdown.') - l.warning(format_binary_to_procs_dict(binary_to_procs_dict)) - l.warning('Killing forcefully...') - for binary, procs in binary_to_procs_dict.items(): - for proc in procs: - l.warning('Killing %s, pid %s', binary, proc.pid) - try: - proc.kill() - except psutil.NoSuchProcess: - pass - delete_cache_files_by_pid(proc.pid) - - delete_s3_shmem() - - except IrodsError as e: - l.info('Failure') - raise e - - l.info('Success') + os.kill(server_pid, signal.SIGTERM if not graceful else signal.SIGQUIT) def restart(self, write_to_stdout=False, test_mode=False): l = logging.getLogger(__name__) l.debug('Calling restart on IrodsController') self.stop() + self.wait_for_server_to_shutdown() self.start(write_to_stdout, test_mode) + self.wait_for_server_to_start() def reload_configuration(self): """Send the SIGHUP signal to the server, causing it to reload the configuration.""" - import signal - server_process = self.get_server_proc() - os.kill(server_process.pid, signal.SIGHUP) - + l = logging.getLogger(__name__) + server_pid = self.get_server_pid() + if server_pid is None: + l.info('iRODS server is not running') + return + os.kill(server_pid, signal.SIGHUP) + # Give the server a chance to stop the listening socket opened by the original agent factory. + time.sleep(1) + self.wait_for_server_to_start() + + # TODO Consider removing this function. Admins should just use "ps". def status(self): l = logging.getLogger(__name__) l.debug('Calling status on IrodsController') @@ -245,6 +156,7 @@ def status(self): else: l.info(format_binary_to_procs_dict(self.get_binary_to_procs_dict(server_proc))) + # TODO Remove this function. Admins should use "ps". def get_binary_to_procs_dict(self, server_proc, server_descendants=None, binaries=None): if server_descendants is None and server_proc is not None and server_proc.is_running(): try: @@ -265,6 +177,41 @@ def get_binary_to_procs_dict(self, server_proc, server_descendants=None, binarie d[b] = procs return d + def wait_for_server_to_start(self, retry_count=100): + l = logging.getLogger(__name__) + irods_port = int(self.config.server_config['zone_port']) + + for _ in range(retry_count): + l.debug('Attempting to connect to iRODS server on port %s. Attempt #%s', irods_port, retry_count) + + with contextlib.closing(socket.socket(socket.AF_INET, socket.SOCK_STREAM)) as s: + if s.connect_ex(('127.0.0.1', irods_port)) == 0: + l.debug('Successfully connected to port %s.', irods_port) + # Raise an exception if we were able to connect to the target port, but attempting + # to capturing the PID of the server returned nothing (i.e. the service account is + # not allowed to send signals to the server listening on the target port). + if self.get_server_pid() is None: + raise IrodsError('iRODS port is bound, but server is not started.') + s.send(b'\x00\x00\x00\x33HEARTBEAT') + message = s.recv(256) + if message == b'HEARTBEAT': + return + l.debug(f'iRODS port returned non-heartbeat message') + + retry_count += 1 + time.sleep(1) + + raise IrodsError('iRODS server failed to start.') + + def wait_for_server_to_shutdown(self, retry_count=100): + l = logging.getLogger(__name__) + for _ in range(retry_count): + l.debug('Waiting for iRODS server to shut down. Attempt #%s', retry_count) + if self.get_server_pid() is None: + return + time.sleep(1) + raise IrodsError('iRODS server failed to shutdown.') + def binary_matches(binary_path, proc): if proc.is_running(): try: @@ -318,29 +265,3 @@ def format_binary_to_procs_dict(proc_dict): os.path.basename(binary), lib.indent(*['Process {0}'.format(proc.pid) for proc in procs]))) return '\n'.join(text_list) - -def delete_cache_files_by_pid(pid): - l = logging.getLogger(__name__) - l.debug('Deleting cache files for pid %s...', pid) - for shm_dir in paths.possible_shm_locations(): - cache_shms = glob.glob(os.path.join( - shm_dir, - '*irods_re_cache*pid{0}_*'.format(pid))) - delete_cache_files_by_name(*cache_shms) - -def delete_cache_files_by_name(*filepaths): - l = logging.getLogger(__name__) - for path in filepaths: - try: - l.debug('Deleting %s', path) - os.unlink(path) - except (IOError, OSError): - l.warning(lib.indent('Error deleting cache file: %s'), path) - -def delete_s3_shmem(): - # delete s3 shared memory if any exist - for shm_dir in paths.possible_shm_locations(): - s3_plugin_shms = glob.glob(os.path.join( - shm_dir, - '*irods_s3-shm*')) - delete_cache_files_by_name(*s3_plugin_shms) diff --git a/scripts/irods/database_interface.py b/scripts/irods/database_interface.py index 14a80a9853..20e010c793 100644 --- a/scripts/irods/database_interface.py +++ b/scripts/irods/database_interface.py @@ -32,7 +32,7 @@ def setup_catalog(irods_config, default_resource_directory=None, default_resourc cursor.rollback() raise -def server_launch_hook(irods_config): +def run_catalog_update(irods_config): l = logging.getLogger(__name__) l.debug('Syncing .odbc.ini file...') def update_catalog_schema(irods_config, cursor): diff --git a/scripts/irods/database_upgrade.py b/scripts/irods/database_upgrade.py index 2dbabbdf81..2d1e21a62f 100644 --- a/scripts/irods/database_upgrade.py +++ b/scripts/irods/database_upgrade.py @@ -189,6 +189,17 @@ def run_update(irods_config, cursor): for option in password_config_dict: database_connect.execute_sql_statement(cursor, statement_str.format(scheme, option, password_config_dict[option])) + elif new_schema_version == 12: + # Increase the size of R_RULE_EXEC.exe_status to 300 characters. This change allows the delay server + # to store the FQDN in the column. This is necessary for making it possible to identify which delay server + # is processing a delay rule. + if irods_config.catalog_database_type == 'postgres': + database_connect.execute_sql_statement(cursor, "alter table R_RULE_EXEC alter column exe_status type varchar(300);") + elif irods_config.catalog_database_type == 'mysql': + database_connect.execute_sql_statement(cursor, "alter table R_RULE_EXEC modify exe_status varchar2(300);") + elif irods_config.catalog_database_type == 'oracle': + database_connect.execute_sql_statement(cursor, "alter table R_RULE_EXEC modify (exe_status varchar2(300));") + else: raise IrodsError('Upgrade to schema version %d is unsupported.' % (new_schema_version)) diff --git a/scripts/irods/json_validation.py b/scripts/irods/json_validation.py deleted file mode 100644 index e8b108a7bc..0000000000 --- a/scripts/irods/json_validation.py +++ /dev/null @@ -1,113 +0,0 @@ -import json -import logging -import pprint -import sys -from urllib.parse import urlparse - -from . import lib -from . import log as irods_log -from .exceptions import IrodsError, IrodsWarning - -try: - import jsonschema -except ImportError: - pass - -try: - import requests -except ImportError: - pass - -def load_and_validate(config_file, schema_uri): - l = logging.getLogger(__name__) - try: - # load configuration file - config_dict = lib.open_and_load_json(config_file) - except (OSError, ValueError) as e: - raise IrodsError('\n\t'.join([ - 'ERROR: Validation Failed for [{0}]:'.format(config_file), - 'against [{0}]'.format(schema_uri), - '{0}: {1}'.format(e.__class__.__name__, e)])) - validate_dict(config_dict, schema_uri, name=config_file) - return config_dict - -def validate_dict(config_dict, schema_uri, name=None): - l = logging.getLogger(__name__) - - if name is None: - name = schema_uri.rpartition('/')[2] - - try: - e = jsonschema.exceptions - except AttributeError as e: - raise IrodsWarning( - 'WARNING: Validation failed for {0} -- jsonschema too old v[{1}]'.format( - name, jsonschema.__version__)) from e - except NameError as e: - raise IrodsWarning( - 'WARNING: Validation failed for {0} -- jsonschema not installed'.format( - name)) from e - - try: - schema = load_json_schema(schema_uri) - l.debug('Validating %s against json schema:', name) - l.debug(pprint.pformat(schema)) - jsonschema.validate(config_dict, schema, resolver=jsonschema.RefResolver(schema_uri, schema)) - - except (jsonschema.exceptions.RefResolutionError, # could not resolve recursive schema $ref - ValueError, # 404s and bad JSON - requests.exceptions.ConnectionError, # network connection error - requests.exceptions.Timeout # timeout - ) as e: - raise IrodsWarning('\n\t'.join([ - 'WARNING: Validation Failed for [{0}]:'.format(name), - 'against [{0}]'.format(schema_uri), - '{0}: {1}'.format(e.__class__.__name__, e)])) - except (jsonschema.exceptions.ValidationError, - jsonschema.exceptions.SchemaError - ) as e: - raise IrodsError('\n\t'.join([ - 'ERROR: Validation Failed for [{0}]:'.format(name), - 'against [{0}]'.format(schema_uri), - '{0}: {1}'.format(e.__class__.__name__, e)])) - - l.info("Validating [%s]... Success", name) - -def load_json_schema(schema_uri): - l = logging.getLogger(__name__) - l.debug('Loading schema from %s', schema_uri) - url_scheme = urlparse(schema_uri).scheme - l.debug('Parsed URL: %s', schema_uri) - scheme_dispatch = { - 'file': load_json_schema_from_file, - 'http': load_json_schema_from_web, - 'https': load_json_schema_from_web - } - - try: - return scheme_dispatch[url_scheme](schema_uri) - except KeyError: - raise IrodsError('ERROR: Invalid schema url: {}'.format(schema_uri)) - -def load_json_schema_from_web(schema_uri): - try: - response = requests.get(schema_uri, timeout=5) - except NameError as e: - raise IrodsError('WARNING: Validation failed for {0} -- requests not installed'.format(name)) from e - - # check response values - try: - # modern requests - schema = json.loads(response.text) - except AttributeError: - # requests pre-v1.0.0 - response.encoding = 'utf8' - schema = json.loads(response.content) - return schema - -def load_json_schema_from_file(schema_uri): - with open(urlparse(schema_uri).path, 'rt') as f: - return json.load(f) - -logging.getLogger('requests.packages.urllib3.connectionpool').addFilter(irods_log.DeferInfoToDebugFilter()) -logging.getLogger('urllib3.connectionpool').addFilter(irods_log.DeferInfoToDebugFilter()) diff --git a/scripts/irods/lib.py b/scripts/irods/lib.py index 72b4f63a0e..20894183bf 100644 --- a/scripts/irods/lib.py +++ b/scripts/irods/lib.py @@ -44,20 +44,6 @@ def get_hostname(): execute_command = execute.execute_command check_command_return = execute.check_command_return -def get_server_pid(): - try: - pid_file = os.path.join(tempfile.gettempdir(), 'irods.pid') - - if os.path.exists(pid_file): - with open(pid_file, 'r') as f: - pid = int(f.readline().strip()) - if psutil.pid_exists(pid): - return pid - except: - pass - - return -1 - def kill_pid(pid): p = psutil.Process(pid) p.suspend() diff --git a/scripts/irods/paths.py b/scripts/irods/paths.py index 0930d5135e..8be150ab76 100644 --- a/scripts/irods/paths.py +++ b/scripts/irods/paths.py @@ -350,6 +350,7 @@ def _icommands_test_directory(): _icommands_test_directory_cache = _irods_directory() / 'clients' / 'icommands' / 'test' return _icommands_test_directory_cache +# TODO Nothing appears to use this or the functions which this is built upon. def icommands_test_directory(): return str(_icommands_test_directory()) @@ -360,6 +361,7 @@ def _server_test_directory(): _server_test_directory_cache = _irods_directory() / 'test' / 'bin' return _server_test_directory_cache +# TODO Nothing appears to use this or the functions which this is built upon. def server_test_directory(): return str(_server_test_directory()) @@ -387,7 +389,7 @@ def default_server_log_path(): def _testmode_server_log_path(): global _testmode_server_log_path_cache if _testmode_server_log_path_cache is None: - _testmode_server_log_path_cache = _log_directory() / 'test_mode_output.log' + _testmode_server_log_path_cache = _irods_directory() / 'test_mode_output.log' return _testmode_server_log_path_cache def testmode_server_log_path(): @@ -425,13 +427,26 @@ def _server_executable(): def server_executable(): return str(_server_executable()) +_agent_executable_cache = None +def _agent_executable(): + global _agent_executable_cache + if _agent_executable_cache is None: + _agent_executable_cache = _server_bin_directory() / 'irodsAgent' + return _agent_executable_cache + +def agent_executable(): + return str(_agent_executable()) + +# TODO Rename to _delay_server_executable_cache _rule_engine_executable_cache = None +# TODO Rename to _delay_server_executable def _rule_engine_executable(): global _rule_engine_executable_cache if _rule_engine_executable_cache is None: _rule_engine_executable_cache = _server_bin_directory() / 'irodsDelayServer' return _rule_engine_executable_cache +# TODO Rename to delay_server_executable def rule_engine_executable(): return str(_rule_engine_executable()) diff --git a/scripts/irods/test/test_control_plane.py b/scripts/irods/test/test_control_plane.py deleted file mode 100644 index d490d71ee7..0000000000 --- a/scripts/irods/test/test_control_plane.py +++ /dev/null @@ -1,270 +0,0 @@ -import copy -import json -import os -import shutil -import tempfile -import time -import unittest - -from . import session -from . import settings -from .. import lib -from .. import paths -from .. import test -from ..configuration import IrodsConfig -from ..controller import IrodsController -from ..test.command import assert_command -from .resource_suite import ResourceBase - -try: - import zmq -except ImportError: - zmq = None - -SessionsMixin = session.make_sessions_mixin([('otherrods','pass')], []) - -class TestControlPlane(SessionsMixin, unittest.TestCase): - - @classmethod - def setUpClass(cls): - # Copy the server config to a temporary file for backup. - config = IrodsConfig() - cls.backup_server_config = tempfile.NamedTemporaryFile(prefix=os.path.basename(config.server_config_path)).name - shutil.copyfile(config.server_config_path, cls.backup_server_config) - - # Get the contents of the server config. - with open(config.server_config_path) as f: - server_config = json.load(f) - - # Update the delay server sleep time to some really long time. This test suite does not use the delay server - # and the nature of the tests are such that any additional connections to the main server can cause problems - # with test clean up whenever the server is restarted. Specifically, it seems that delay server queries for - # work to do can cause shared memory cleanup problems, so we can just avoid them by effectively disabling the - # delay server. - server_config['advanced_settings']['delay_server_sleep_time_in_seconds'] = 7200 - lib.update_json_file_from_dict(config.server_config_path, server_config) - - IrodsController().restart(test_mode=True) - - @classmethod - def tearDownClass(cls): - # Copy the backed up server config back to where it belongs and restart the server. - config = IrodsConfig() - shutil.copyfile(cls.backup_server_config, config.server_config_path) - IrodsController().restart(test_mode=True) - - def test_pause_and_resume(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - admin_session.assert_icommand('irods-grid pause --all', 'STDOUT_SINGLELINE', 'pausing') - - # need a time-out assert icommand for ils here - - admin_session.assert_icommand('irods-grid resume --all', 'STDOUT_SINGLELINE', 'resuming') - - # Make sure server is actually responding - admin_session.assert_icommand('ils', 'STDOUT_SINGLELINE', IrodsConfig().client_environment['irods_zone_name']) - admin_session.assert_icommand('irods-grid status --all', 'STDOUT_SINGLELINE', 'hosts') - - def test_status(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - admin_session.assert_icommand('irods-grid status --all', 'STDOUT_SINGLELINE', 'hosts') - - def test_hosts_separator(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - for s in [',', ', ']: - hosts_string = s.join([lib.get_hostname()]*2) - admin_session.assert_icommand(['irods-grid', 'status', '--hosts', hosts_string], 'STDOUT_SINGLELINE', lib.get_hostname()) - - @unittest.skipIf(test.settings.RUN_IN_TOPOLOGY, 'Skip for Topology Testing: No way to restart grid') - def test_shutdown(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - if 'irods_rule_engine_plugin-irods_rule_language' in IrodsConfig().configured_rule_engine_plugins: - self.assertTrue(lib.re_shm_exists()) - - try: - assert_command('irods-grid shutdown --all', 'STDOUT_SINGLELINE', 'shutting down') - #time.sleep(2) - assert_command('ils', 'STDERR_SINGLELINE', 'connectToRhost error') - - self.assertFalse(lib.re_shm_exists()) - finally: - IrodsController().start() - - def test_shutdown_local_server(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - initial_size_of_server_log = lib.get_file_size_by_path(IrodsConfig().server_parent_log_path) - try: - admin_session.assert_icommand(['irods-grid', 'shutdown', '--hosts', lib.get_hostname()], 'STDOUT_SINGLELINE', 'shutting down') - lib.delayAssert( - lambda: lib.log_message_occurrences_equals_count( - msg='iRODS Server is done', - server_log_path=IrodsConfig().server_parent_log_path, - start_index=initial_size_of_server_log)) - finally: - IrodsController().start() - - def test_shutdown_local_server_using_hostname_alias_defined_in_server_config__issue_5700(self): - try: - with lib.file_backed_up(paths.server_config_path()): - hostname_alias = 'hostname-alias.issue-5700' - - with open(paths.server_config_path(), 'r+') as f: - server_config = json.load(f) - server_config['host_resolution']['host_entries'] = [{ - 'address_type': 'local', - 'addresses': [ - lib.get_hostname(), - hostname_alias - ] - }] - - f.seek(0) - f.truncate(0) - f.write(json.dumps(server_config, indent=4)) - - # Restart the server so that the original process (parent of the agent factory) - # sees the updates to the server_config.json file. - IrodsController().restart(test_mode=True) - - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - initial_size_of_server_log = lib.get_file_size_by_path(IrodsConfig().server_parent_log_path) - - expected_output = [json.dumps({'hosts': [{'shutting down': lib.get_hostname()}]}, indent=4)] - admin_session.assert_icommand(['irods-grid', 'shutdown', '--hosts', hostname_alias], 'STDOUT', expected_output) - lib.delayAssert( - lambda: lib.log_message_occurrences_equals_count( - msg='iRODS Server is done', - server_log_path=IrodsConfig().server_parent_log_path, - start_index=initial_size_of_server_log)) - - finally: - # Restart the server so that the original process (parent of the agent factory) - # sees the restored server_config.json file. - IrodsController().restart(test_mode=True) - - def test_control_plane_uses_whole_string_matching_for_hostname_validation__issue_5700(self): - try: - with lib.file_backed_up(paths.server_config_path()): - hostname_alias = 'hostname-alias.issue-5700.' + lib.get_hostname() - - with open(paths.server_config_path(), 'r+') as f: - server_config = json.load(f) - server_config['host_resolution']['host_entries'] = [{ - 'address_type': 'local', - 'addresses': [ - lib.get_hostname(), - hostname_alias - ] - }] - - f.seek(0) - f.truncate(0) - f.write(json.dumps(server_config, indent=4)) - - # Restart the server so that the original process (parent of the agent factory) - # sees the updates to the server_config.json file. - IrodsController().restart(test_mode=True) - - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - - # Show that hostnames passed to the --hosts option must match a name - # in hosts_config.json exactly. Hostnames containing dots and ending in - # "." results in an error. See the following comment for - # more information: - # - # https://github.com/irods/irods/issues/5700#issuecomment-963390725 - # - invalid_hostname_alias = 'invalid.' + lib.get_hostname() - expected_output = ['server responded with an error'] - admin_session.assert_icommand(['irods-grid', 'shutdown', '--hosts', invalid_hostname_alias], 'STDERR', expected_output) - - # Show that the server is still running. - admin_session.assert_icommand(['irods-grid', 'status', '--hosts', hostname_alias], 'STDOUT_SINGLELINE', ['server_state_running']) - - finally: - # Restart the server so that the original process (parent of the agent factory) - # sees the restored server_config.json file. - IrodsController().restart(test_mode=True) - - def test_invalid_client_environment(self): - env_backup = copy.deepcopy(self.admin_sessions[0].environment_file_contents) - - if 'irods_server_control_plane_port' in self.admin_sessions[0].environment_file_contents: - del self.admin_sessions[0].environment_file_contents['irods_server_control_plane_port'] - if 'irods_server_control_plane_key' in self.admin_sessions[0].environment_file_contents: - del self.admin_sessions[0].environment_file_contents['irods_server_control_plane_key'] - if 'irods_server_control_plane_encryption_num_hash_rounds' in self.admin_sessions[0].environment_file_contents: - del self.admin_sessions[0].environment_file_contents['irods_server_control_plane_encryption_num_hash_rounds'] - if 'irods_server_control_plane_port' in self.admin_sessions[0].environment_file_contents: - del self.admin_sessions[0].environment_file_contents['irods_server_control_plane_encryption_algorithm'] - - self.admin_sessions[0].assert_icommand('irods-grid status --all', 'STDERR_SINGLELINE', 'USER_INVALID_CLIENT_ENVIRONMENT') - - self.admin_sessions[0].environment_file_contents = env_backup - - def test_status_with_invalid_host(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - try: - admin_session.assert_icommand('iadmin mkresc invalid_resc unixfilesystem invalid_host:/tmp/irods/invalid', 'STDOUT_SINGLELINE', 'not a valid DNS entry') - _, stdout, _ = admin_session.assert_icommand(['irods-grid', 'status', '--all'], 'STDOUT_SINGLELINE') - host_responses = json.loads(stdout)['hosts'] - self.assertTrue(({'failed_to_connect': 'tcp://invalid_host:1248'} in host_responses) or ({'response_message_is_empty_from': 'tcp://invalid_host:1248'} in host_responses)) - finally: - admin_session.assert_icommand('iadmin rmresc invalid_resc') - - @unittest.skipIf(test.settings.RUN_IN_TOPOLOGY, 'Skip for Topology Testing: No way to restart grid') - def test_shutdown_with_invalid_host(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - try: - admin_session.assert_icommand('iadmin mkresc invalid_resc unixfilesystem invalid_host:/tmp/irods/invalid', 'STDOUT_SINGLELINE', 'not a valid DNS entry') - try: - _, stdout, _ = admin_session.assert_icommand(['irods-grid', 'shutdown', '--wait-forever', '--all'], 'STDOUT_SINGLELINE', 'shutting down') - host_responses = json.loads(stdout)['hosts'] - self.assertTrue(({'failed_to_connect': 'tcp://invalid_host:1248'} in host_responses) or ({'response_message_is_empty_from': 'tcp://invalid_host:1248'} in host_responses)) - self.admin_sessions[0].assert_icommand('ils','STDERR_SINGLELINE','connectToRhost error') - finally: - # TODO: Restart here in case the main server isn't finished tearing down yet. - # We should only need to perform a start() here, though. - #IrodsController().start() - IrodsController().restart(test_mode=True) - finally: - admin_session.assert_icommand('iadmin rmresc invalid_resc') - - @unittest.skipIf(test.settings.RUN_IN_TOPOLOGY, 'Skip for Topology Testing') - def test_provider_shutdown_with_no_consumer(self): - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = IrodsConfig().client_environment - try: - admin_session.assert_icommand('iadmin rmresc demoResc') - host_name = lib.get_hostname() - admin_session.assert_icommand(['irods-grid', 'status', '--hosts', host_name], 'STDOUT_SINGLELINE', host_name) - finally: - host_name = lib.get_hostname() - admin_session.assert_icommand('iadmin mkresc demoResc unixfilesystem '+host_name+':/var/lib/irods/Vault', 'STDOUT_SINGLELINE', 'demoResc') - - @unittest.skipIf(zmq is None, 'Skip if pyzmq is unavailable') - def test_empty_message(self): - config = IrodsConfig() - context = zmq.Context() - socket = context.socket(zmq.REQ) - control_plane_endpoint = "tcp://%s:%s" % ( - config.client_environment['irods_host'], - config.server_config['server_control_plane_port']) - socket.connect(control_plane_endpoint) - socket.send("") - - with session.make_session_for_existing_admin() as admin_session: - admin_session.environment_file_contents = config.client_environment - _, out, _ = admin_session.assert_icommand('irods-grid status --hosts=%s' % config.client_environment['irods_host'], 'STDOUT_SINGLELINE', "") - status_dict = json.loads(out) - assert status_dict['hosts'][0]['status'] == 'server_state_running' - diff --git a/scripts/irods/upgrade_configuration.py b/scripts/irods/upgrade_configuration.py index e4df33dde6..ec772d214f 100644 --- a/scripts/irods/upgrade_configuration.py +++ b/scripts/irods/upgrade_configuration.py @@ -214,6 +214,23 @@ def update_base(base, updates): return new_server_config +def convert_to_v5_schema_and_add_missing_properties(server_config): + def update_base(base, updates): + for k, v in updates.items(): + if isinstance(v, collections.abc.Mapping): + base[k] = update_base(base.get(k, {}), v) + else: + base[k] = v + return base + + # Load server_config.json.template as our base. + with open(paths.get_template_filepath(paths.server_config_path())) as f: + base = json.load(f) + + new_server_config = update_base(base, server_config) + + return new_server_config + def upgrade_config_file(irods_config, path, new_version, schema_name=None): l = logging.getLogger(__name__) @@ -315,6 +332,10 @@ def run_schema_update(config_dict, schema_name, next_schema_version): merge_hosts_config_into_server_config(config_dict) merge_host_access_control_config_into_server_config(config_dict) + if 5 == next_schema_version: + if 'server_config' == schema_name: + config_dict = convert_to_v5_schema_and_add_missing_properties(config_dict) + config_dict['schema_version'] = 'v%d' % (next_schema_version) return config_dict diff --git a/scripts/irods_control.py b/scripts/irods_control.py index 4d3e6702be..9b59a9cab1 100755 --- a/scripts/irods_control.py +++ b/scripts/irods_control.py @@ -68,12 +68,13 @@ def main(): operations_dict = {} operations_dict['start'] = lambda: irods_controller.start(write_to_stdout=options.write_to_stdout, test_mode=options.test_mode) - operations_dict['graceful_start'] = lambda: irods_controller.start(write_to_stdout=options.write_to_stdout, test_mode=options.test_mode) operations_dict['stop'] = lambda: irods_controller.stop() + operations_dict['graceful_stop'] = lambda: irods_controller.stop(graceful=True) + operations_dict['reload'] = lambda: irods_controller.reload_configuration() operations_dict['restart'] = lambda: irods_controller.restart(write_to_stdout=options.write_to_stdout, test_mode=options.test_mode) - operations_dict['graceful_restart'] = lambda: irods_controller.restart(write_to_stdout=options.write_to_stdout, test_mode=options.test_mode) operations_dict['status'] = lambda: irods_controller.status() operations_dict['get_environment'] = lambda: irods_config.print_execution_environment() + operations_dict['upgrade'] = lambda: irods_controller.upgrade() (options, arguments) = parse_options() if len(arguments) != 1: diff --git a/scripts/pid_age.py b/scripts/pid_age.py deleted file mode 100755 index a4807509e6..0000000000 --- a/scripts/pid_age.py +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/python3 -from __future__ import print_function -import sys -import psutil -import time - -def get_create_time(process): - if psutil.version_info >= (2,0): - return process.create_time() - return process.create_time - -pid = int(sys.argv[1]) -p = psutil.Process(pid) -print(time.time() - get_create_time(p)) diff --git a/scripts/setup_irods.py b/scripts/setup_irods.py index 8308b08e4d..bc9e865154 100755 --- a/scripts/setup_irods.py +++ b/scripts/setup_irods.py @@ -130,6 +130,8 @@ def setup_server(irods_config, json_configuration_file=None, test_mode=False): from irods import database_interface l.info(irods.lib.get_header('Setting up the database')) database_interface.setup_catalog(irods_config, default_resource_directory=default_resource_directory, default_resource_name=default_resource_name) + l.info(irods.lib.get_header('Applying updates to database')) + database_interface.run_catalog_update(irods_config) l.info(irods.lib.get_header('Starting iRODS...')) IrodsController(irods_config).start(test_mode=test_mode) diff --git a/scripts/system_identification.py b/scripts/system_identification.py deleted file mode 100644 index f42f0a3ec2..0000000000 --- a/scripts/system_identification.py +++ /dev/null @@ -1,39 +0,0 @@ -#!/usr/bin/python3 -from __future__ import print_function - -import inspect -import distro -import platform -import sys - -# The following url lists identifying information from several systems -# https://github.com/hpcugent/easybuild/wiki/OS_flavor_name_version - -# Invoke module functions directly from the command line by passing the -# desired function name minus the leading 'get_' as the first argument. - -def get_os_distribution_name(): - system = platform.system() - if system == 'Linux' : return distro.id() - if system == 'Darwin': return 'MacOSX' - -def get_os_distribution_version(): - system = platform.system() - if system == 'Linux' : return distro.version() - if system == 'Darwin': return platform.mac_ver()[0] - -if __name__ == '__main__': - def print_error(*args, **kwargs): - print(*args, file=sys.stderr, **kwargs) - - argument_function_map = dict((fname[4:], f) - for fname, f in globals().items() - if fname.startswith('get_') and inspect.isfunction(f)) - if len(sys.argv) != 2 or sys.argv[1] not in argument_function_map: - print_error('Call as one of the following:') - for arg in argument_function_map: - print_error(' {0} {1}'.format(sys.argv[0], arg)) - print_error('Was called with sys.argv {0}'.format(sys.argv)) - sys.exit(1) - - print(argument_function_map[sys.argv[1]]()) diff --git a/scripts/validate_json.py b/scripts/validate_json.py deleted file mode 100755 index 2827101415..0000000000 --- a/scripts/validate_json.py +++ /dev/null @@ -1,31 +0,0 @@ -#!/usr/bin/python3 -from __future__ import print_function -import logging -import sys - -from irods import json_validation -from irods.exceptions import IrodsError, IrodsWarning -import irods.log - -if __name__ == '__main__': - logging.getLogger().setLevel(logging.NOTSET) - l = logging.getLogger(__name__) - - irods.log.register_tty_handler(sys.stdout, logging.INFO, logging.WARNING) - irods.log.register_tty_handler(sys.stderr, logging.WARNING, None) - if len(sys.argv) != 3: - l.error('Usage: %s ', sys.argv[0]) - sys.exit(1) - - config_file = sys.argv[1] - schema_uri = sys.argv[2] - - try: - json_validation.load_and_validate(config_file, schema_uri) - except IrodsWarning as e: - l.warning('Encountered a warning in validation.', exc_info=True) - sys.exit(2) - except IrodsError as e: - l.error('Encountered an error in validation.', exc_info=True) - sys.exit(1) - sys.exit(0) diff --git a/server/CMakeLists.txt b/server/CMakeLists.txt index b729d7b375..f7fd2550e5 100644 --- a/server/CMakeLists.txt +++ b/server/CMakeLists.txt @@ -20,11 +20,9 @@ add_subdirectory(icat) add_subdirectory(re) add_subdirectory(drivers) add_subdirectory(auth) -add_subdirectory(control_plane) add_subdirectory(main_server) add_subdirectory(hostname_resolves_to_local_address) add_subdirectory(delay_server) -add_subdirectory(irods_grid) add_subdirectory(harness) target_link_objects( @@ -87,34 +85,6 @@ install( NAMELINK_COMPONENT ${IRODS_PACKAGE_COMPONENT_DEVELOPMENT_NAME} ) -# Initialize IRODS_CONFIGURATION_SCHEMA_PREFIX for configuration schemas. -# This variable helps to produce the correct URI for the "$id" JSON property. -if (IS_ABSOLUTE "${IRODS_HOME_DIRECTORY}") - set(IRODS_CONFIGURATION_SCHEMA_PREFIX "") -else() - if (CMAKE_INSTALL_PREFIX STREQUAL "/") - # This branch is primarily for packaged installations, but can work for non-package - # installs as well. - set(IRODS_CONFIGURATION_SCHEMA_PREFIX "") - elseif (NOT CMAKE_INSTALL_PREFIX STREQUAL "") - # This branch is for non-package installations. - if (NOT CMAKE_INSTALL_PREFIX MATCHES ".+/$") - set(IRODS_CONFIGURATION_SCHEMA_PREFIX "${CMAKE_INSTALL_PREFIX}/") - endif() - - # Remove the leading slash so that the protocol matches what we expect. - # i.e. file:///non-package-install-root instead of file:////non-package-install-root - if (IS_ABSOLUTE "${IRODS_CONFIGURATION_SCHEMA_PREFIX}") - string(SUBSTRING "${IRODS_CONFIGURATION_SCHEMA_PREFIX}" 1 -1 NEW_IRODS_CONFIGURATION_SCHEMA_PREFIX) - set(IRODS_CONFIGURATION_SCHEMA_PREFIX "${NEW_IRODS_CONFIGURATION_SCHEMA_PREFIX}") - endif() - else() - message(FATAL_ERROR "Cannot initialize IRODS_CONFIGURATION_SCHEMA_PREFIX. CMAKE_INSTALL_PREFIX is empty.") - endif() -endif() - -# Install configuration schemas with updated id field -set(IRODS_CONFIGURATION_SCHEMA_VERSION 4) set( IRODS_CONFIGURATION_SCHEMA_FILES version.json @@ -122,7 +92,6 @@ set( client_hints.json configuration_directory.json database_config.json - grid_status.json host_access_control.json host_resolution.json plugin.json @@ -136,11 +105,11 @@ set( zone_bundle.json ) foreach(SCHEMA_FILE IN LISTS IRODS_CONFIGURATION_SCHEMA_FILES) - configure_file( + configure_file( "${CMAKE_IRODS_SOURCE_DIR}/schemas/configuration/v${IRODS_CONFIGURATION_SCHEMA_VERSION}/${SCHEMA_FILE}.in" - "${CMAKE_IRODS_BINARY_DIR}/schemas/configuration/v${IRODS_CONFIGURATION_SCHEMA_VERSION}/${SCHEMA_FILE}" - @ONLY - ) + "${CMAKE_IRODS_BINARY_DIR}/schemas/configuration/v${IRODS_CONFIGURATION_SCHEMA_VERSION}/${SCHEMA_FILE}" + @ONLY + ) install( FILES "${CMAKE_IRODS_BINARY_DIR}/schemas/configuration/v${IRODS_CONFIGURATION_SCHEMA_VERSION}/${SCHEMA_FILE}" @@ -163,6 +132,7 @@ install( COMPONENT ${IRODS_PACKAGE_COMPONENT_SERVER_NAME} ) +# TODO What is this for? install( DIRECTORY DESTINATION "${IRODS_HOME_DIRECTORY}/config/packedRei" @@ -187,6 +157,16 @@ install( COMPONENT ${IRODS_PACKAGE_COMPONENT_SERVER_NAME} ) +install( + DIRECTORY + DESTINATION "${CMAKE_INSTALL_RUNSTATEDIR}/irods" + DIRECTORY_PERMISSIONS + OWNER_EXECUTE OWNER_WRITE OWNER_READ + GROUP_EXECUTE GROUP_WRITE GROUP_READ + WORLD_READ + COMPONENT ${IRODS_PACKAGE_COMPONENT_SERVER_NAME} +) + install( FILES "${CMAKE_IRODS_SOURCE_DIR}/README.md" @@ -247,7 +227,7 @@ install( install( FILES "${CMAKE_IRODS_SOURCE_DIR}/msiExecCmd_bin/test_execstream.py" - "${CMAKE_IRODS_SOURCE_DIR}/msiExecCmd_bin/hello" + "${CMAKE_IRODS_SOURCE_DIR}/msiExecCmd_bin/hello" # TODO Is this needed? DESTINATION "${IRODS_HOME_DIRECTORY}/msiExecCmd_bin" COMPONENT ${IRODS_PACKAGE_COMPONENT_SERVER_NAME} PERMISSIONS OWNER_READ OWNER_EXECUTE GROUP_READ WORLD_READ diff --git a/server/api/CMakeLists.txt b/server/api/CMakeLists.txt index e4a1b9fbb7..75463a61c3 100644 --- a/server/api/CMakeLists.txt +++ b/server/api/CMakeLists.txt @@ -5,6 +5,8 @@ add_library( "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_atomic_apply_metadata_operations.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_check_auth_credentials.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_data_object_finalize.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_delay_rule_tag.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_delay_rule_tag_clear.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_genquery2.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_get_delay_rule_info.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rs_get_file_descriptor_info.cpp" @@ -213,6 +215,8 @@ install( "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_atomic_apply_metadata_operations.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_check_auth_credentials.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_data_object_finalize.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_delay_rule_tag.hpp" + "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_delay_rule_tag_clear.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_genquery2.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_get_delay_rule_info.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rs_get_file_descriptor_info.hpp" diff --git a/server/api/include/irods/rs_delay_rule_tag.hpp b/server/api/include/irods/rs_delay_rule_tag.hpp new file mode 100644 index 0000000000..cc0056ed0a --- /dev/null +++ b/server/api/include/irods/rs_delay_rule_tag.hpp @@ -0,0 +1,15 @@ +#ifndef IRODS_RS_DELAY_RULE_TAG_HPP +#define IRODS_RS_DELAY_RULE_TAG_HPP + +/// \file + +#include "irods/delay_rule_tag.h" + +struct RsComm; + +/// TODO +/// +/// \since 5.0.0 +int rs_delay_rule_tag(RsComm* _comm, DelayRuleTagInput* _input); + +#endif // IRODS_RS_DELAY_RULE_TAG_HPP diff --git a/server/api/include/irods/rs_delay_rule_tag_clear.hpp b/server/api/include/irods/rs_delay_rule_tag_clear.hpp new file mode 100644 index 0000000000..b358f11fd0 --- /dev/null +++ b/server/api/include/irods/rs_delay_rule_tag_clear.hpp @@ -0,0 +1,15 @@ +#ifndef IRODS_RS_DELAY_RULE_TAG_CLEAR_HPP +#define IRODS_RS_DELAY_RULE_TAG_CLEAR_HPP + +/// \file + +#include "irods/delay_rule_tag_clear.h" + +struct RsComm; + +/// TODO +/// +/// \since 5.0.0 +int rs_delay_rule_tag_clear(RsComm* _comm, DelayRuleTagClearInput* _input); + +#endif // IRODS_RS_DELAY_RULE_TAG_CLEAR_HPP diff --git a/server/api/src/rsExecCmd.cpp b/server/api/src/rsExecCmd.cpp index c8ce5e4bf0..143cf9ee8c 100644 --- a/server/api/src/rsExecCmd.cpp +++ b/server/api/src/rsExecCmd.cpp @@ -1,31 +1,32 @@ -/*** Copyright (c), The Regents of the University of California *** - *** For more information please refer to files in the COPYRIGHT directory ***/ -/* This is script-generated code (for the most part). */ -/* See dataObjRead.h for a description of this API call.*/ +#include "irods/rsExecCmd.hpp" -#include -#ifndef windows_platform -#include -#else -#include "irods/Unix2Nt.hpp" -#endif -#include "irods/execCmd.h" -#include "irods/objMetaOpr.hpp" #include "irods/dataObjOpr.hpp" +#include "irods/execCmd.h" #include "irods/fileClose.h" +#include "irods/icatDefines.h" +#include "irods/irods_default_paths.hpp" +#include "irods/irods_re_structs.hpp" +#include "irods/irods_resource_backport.hpp" +#include "irods/irods_resource_redirect.hpp" +#include "irods/irods_server_properties.hpp" #include "irods/miscServerFunct.hpp" +#include "irods/objMetaOpr.hpp" +#include "irods/rcGlobalExtern.h" #include "irods/rodsLog.h" -#include "irods/icatDefines.h" #include "irods/rsGlobalExtern.hpp" -#include "irods/rcGlobalExtern.h" -#include "irods/rsExecCmd.hpp" -#include "irods/irods_resource_backport.hpp" -#include "irods/irods_resource_redirect.hpp" -#include "irods/irods_re_structs.hpp" -#include "irods/irods_default_paths.hpp" +#include + +#ifndef windows_platform +#include +#else +#include "irods/Unix2Nt.hpp" +#endif #include + +#include + boost::mutex ExecCmdMutex; int initExecCmdMutex() { return 0; @@ -369,7 +370,7 @@ execCmd( execCmd_t *execCmdInp, int stdOutFd, int stdErrFd ) { char *av[LONG_NAME_LEN]; int status; - const auto cmd_path = irods::get_irods_home_directory().append("msiExecCmd_bin").append(execCmdInp->cmd); + const auto cmd_path = irods::get_irods_msiExecCmd_bin_directory() / execCmdInp->cmd; std::strncpy(cmdPath, cmd_path.c_str(), LONG_NAME_LEN); rodsLog( LOG_NOTICE, "execCmd:%s argv:%s", cmdPath, execCmdInp->cmdArgv ); initCmdArg( av, execCmdInp->cmdArgv, cmdPath ); diff --git a/server/api/src/rsProcStat.cpp b/server/api/src/rsProcStat.cpp index b864e7d0fb..83fd7bd7bd 100644 --- a/server/api/src/rsProcStat.cpp +++ b/server/api/src/rsProcStat.cpp @@ -1,3 +1,5 @@ +#include "irods/irods_default_paths.hpp" +#include "irods/irods_server_properties.hpp" #include "irods/rcMisc.h" #include "irods/procStat.h" #include "irods/objMetaOpr.hpp" @@ -6,16 +8,71 @@ #include "irods/rodsLog.h" #include "irods/rsGlobalExtern.hpp" #include "irods/rcGlobalExtern.h" -#include "irods/procLog.h" #include "irods/rsProcStat.hpp" #include "irods/irods_resource_backport.hpp" #include #include +#include #include -using namespace boost::filesystem; +using namespace boost::filesystem; // TODO Open issue for this. + +namespace +{ + int readProcLog(int pid, procLog_t* procLog) + { + using log_api = irods::experimental::log::api; + + if (nullptr == procLog) { + log_api::error("{}: Invalid input argument: nullptr", __func__); + return USER__NULL_INPUT_ERR; + } + + const auto proc_dir = irods::get_irods_proc_directory(); + const auto agent_info_path = proc_dir / std::to_string(pid); + + std::ifstream procStream{agent_info_path.c_str()}; + std::vector procTokens; + while (!procStream.eof() && procTokens.size() < 7) { + std::string token; + procStream >> token; + log_api::debug("{}: Adding token to agent proc info: [{}]", __func__, token); + procTokens.push_back(std::move(token)); + } + + if (procTokens.size() != 7) { + log_api::error("{}: Agent process info: [{}], number of parameters: [{}]", __func__, agent_info_path.c_str(), procTokens.size()); + return UNIX_FILE_READ_ERR; + } + + procLog->pid = pid; + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::snprintf(procLog->clientName, sizeof(procLog->clientName), "%s", procTokens[0].c_str()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::snprintf(procLog->clientZone, sizeof(procLog->clientZone), "%s", procTokens[1].c_str()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::snprintf(procLog->proxyName, sizeof(procLog->proxyName), "%s", procTokens[2].c_str()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::snprintf(procLog->proxyZone, sizeof(procLog->proxyZone), "%s", procTokens[3].c_str()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::snprintf(procLog->progName, sizeof(procLog->progName), "%s", procTokens[4].c_str()); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + std::snprintf(procLog->remoteAddr, sizeof(procLog->remoteAddr), "%s", procTokens[5].c_str()); + + try { + procLog->startTime = boost::lexical_cast(procTokens[6].c_str()); + } + catch (const std::exception& e) { + log_api::error("{}: Could not convert [{}] to unsigned int.", __func__, procTokens[6]); + return INVALID_LEXICAL_CAST; + } + + return 0; + } // readProcLog +} // anonymous namespace int rsProcStat( rsComm_t * rsComm, procStatInp_t * procStatInp, @@ -181,7 +238,8 @@ localProcStat( procStatInp_t *procStatInp, char childPath[MAX_NAME_LEN]; int count = 0; - numProc = getNumFilesInDir( ProcLogDir ) + 2; /* add 2 to give some room */ + const auto proc_dir = irods::get_irods_proc_directory(); + numProc = getNumFilesInDir(proc_dir.c_str()) + 2; /* add 2 to give some room */ std::memset(&procLog, 0, sizeof(procLog)); /* init serverAddr */ @@ -202,11 +260,11 @@ localProcStat( procStatInp_t *procStatInp, } /* loop through the directory */ - path srcDirPath( ProcLogDir ); + path srcDirPath(proc_dir); if ( !exists( srcDirPath ) || !is_directory( srcDirPath ) ) { status = USER_INPUT_PATH_ERR - errno; rodsLogError( LOG_ERROR, status, - "localProcStat: opendir local dir error for %s", ProcLogDir ); + "localProcStat: opendir local dir error for %s", proc_dir.c_str()); return status; } directory_iterator end_itr; // default construction yields past-the-end diff --git a/server/api/src/rsServerReport.cpp b/server/api/src/rsServerReport.cpp index e3dec74c47..e67db10d0f 100644 --- a/server/api/src/rsServerReport.cpp +++ b/server/api/src/rsServerReport.cpp @@ -251,27 +251,11 @@ irods::error get_host_system_information(json& _host_system_information) _host_system_information["uname"] = nullptr; } - std::vector args; - args.push_back( "os_distribution_name" ); - std::string os_distribution_name; - ret = get_script_output_single_line( "python3", "system_identification.py", args, os_distribution_name ); - if ( ret.ok() ) { - _host_system_information["os_distribution_name"] = os_distribution_name; - } else { - irods::log( PASS( ret ) ); - _host_system_information["os_distribution_name"] = nullptr; - } - - args.clear(); - args.push_back( "os_distribution_version" ); - std::string os_distribution_version; - ret = get_script_output_single_line( "python3", "system_identification.py", args, os_distribution_version ); - if (ret.ok()) { - _host_system_information["os_distribution_version"] = os_distribution_version; - } else { - irods::log( PASS( ret ) ); - _host_system_information["os_distribution_version"] = nullptr; - } + // These properties are included for backwards compatibility. They are set to + // empty strings to avoid issues with systems which expect a valid string. Systems + // which rely on this information should parse the content of the uname property. + _host_system_information["os_distribution_name"] = ""; + _host_system_information["os_distribution_version"] = ""; return SUCCESS(); } // get_host_system_information @@ -432,19 +416,11 @@ irods::error get_config_dir( json& _cfg_dir ) irods::error load_version_file( json& _version ) { - // =-=-=-=-=-=-=- // if json file exists, simply load that - fs::path version_file; - try { - version_file = irods::get_irods_home_directory(); - } catch (const irods::exception& e) { - irods::log(e); - return ERROR(-1, "failed to get irods home directory"); - } - version_file.append("version.json"); + const auto version_file = irods::get_server_property("version_file"); - if ( fs::exists( version_file ) ) { - return load_json_file(version_file.generic_string(), _version); + if (fs::exists(version_file)) { + return load_json_file(version_file, _version); } return SUCCESS(); diff --git a/server/api/src/rs_delay_rule_tag.cpp b/server/api/src/rs_delay_rule_tag.cpp new file mode 100644 index 0000000000..004a8fb703 --- /dev/null +++ b/server/api/src/rs_delay_rule_tag.cpp @@ -0,0 +1,73 @@ +#include "irods/rs_delay_rule_tag.hpp" + +//#include "irods/apiHandler.hpp" +#include "irods/catalog_utilities.hpp" +#include "irods/irods_logger.hpp" +//#include "irods/irods_rs_comm_query.hpp" +//#include "irods/irods_server_properties.hpp" +#include "irods/irods_version.h" +#include "irods/rodsConnect.h" +#include "irods/rodsDef.h" +#include "irods/rodsErrorTable.h" +#include "irods/icatHighLevelRoutines.hpp" +#include "irods/stringOpr.h" + +namespace +{ + using log_api = irods::experimental::log::api; +} // anonymous namespace + +auto rs_delay_rule_tag(RsComm* _comm, DelayRuleTagInput* _input) -> int +{ + if (!_comm || !_input) { + log_api::error("{}: Invalid input: received null pointer.", __func__); + return SYS_INTERNAL_NULL_INPUT_ERR; + } + + if (!is_non_empty_string(_input->rule_id, sizeof(DelayRuleTagInput::rule_id))) { + log_api::error("{}: Rule ID must be a non-empty string.", __func__); + return SYS_INVALID_INPUT_PARAM; + } + + if (!is_non_empty_string(_input->tag, sizeof(DelayRuleTagInput::tag))) { + log_api::error("{}: FQDN must be a non-empty string.", __func__); + return SYS_INVALID_INPUT_PARAM; + } + + rodsServerHost* host_info{}; + + try { + host_info = irods::experimental::catalog::redirect_to_catalog_provider(*_comm, nullptr); + } + catch (const irods::exception& e) { + log_api::error("{}: Catalog provider host resolution error: {}", __func__, e.client_display_what()); + return e.code(); // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) + } + + if (host_info->localFlag != LOCAL_HOST) { + log_api::trace("{}: Redirecting request to catalog service provider.", __func__); + return rc_delay_rule_tag(host_info->conn, _input); + } + + // + // At this point, we assume we're connected to the catalog service provider in the correct zone. + // + + try { + const auto ec = chl_delay_rule_tag(*_comm, _input->rule_id, _input->tag); + + if (ec < 0) { + log_api::error("{}: chl_delay_rule_tag failed with error code [{}].", __func__, ec); + } + + return ec; + } + catch (const irods::exception& e) { + log_api::error("{}: {}", __func__, e.client_display_what()); + return e.code(); // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) + } + catch (const std::exception& e) { + log_api::error("{}: {}", __func__, e.what()); + return SYS_LIBRARY_ERROR; + } +} // rs_delay_rule_tag diff --git a/server/api/src/rs_delay_rule_tag_clear.cpp b/server/api/src/rs_delay_rule_tag_clear.cpp new file mode 100644 index 0000000000..91a1d949b8 --- /dev/null +++ b/server/api/src/rs_delay_rule_tag_clear.cpp @@ -0,0 +1,68 @@ +#include "irods/rs_delay_rule_tag_clear.hpp" + +//#include "irods/apiHandler.hpp" +#include "irods/catalog_utilities.hpp" +#include "irods/irods_logger.hpp" +//#include "irods/irods_rs_comm_query.hpp" +//#include "irods/irods_server_properties.hpp" +#include "irods/irods_version.h" +#include "irods/rodsConnect.h" +#include "irods/rodsDef.h" +#include "irods/rodsErrorTable.h" +#include "irods/icatHighLevelRoutines.hpp" +#include "irods/stringOpr.h" + +namespace +{ + using log_api = irods::experimental::log::api; +} // anonymous namespace + +auto rs_delay_rule_tag_clear(RsComm* _comm, DelayRuleTagClearInput* _input) -> int +{ + if (!_comm || !_input) { + log_api::error("{}: Invalid input: received null pointer.", __func__); + return SYS_INTERNAL_NULL_INPUT_ERR; + } + + if (!is_non_empty_string(_input->rule_id, sizeof(DelayRuleTagClearInput::rule_id))) { + log_api::error("{}: Rule ID must be a non-empty string.", __func__); + return SYS_INVALID_INPUT_PARAM; + } + + rodsServerHost* host_info{}; + + try { + host_info = irods::experimental::catalog::redirect_to_catalog_provider(*_comm, nullptr); + } + catch (const irods::exception& e) { + log_api::error("{}: Catalog provider host resolution error: {}", __func__, e.client_display_what()); + return e.code(); // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) + } + + if (host_info->localFlag != LOCAL_HOST) { + log_api::trace("{}: Redirecting request to catalog service provider.", __func__); + return rc_delay_rule_tag_clear(host_info->conn, _input); + } + + // + // At this point, we assume we're connected to the catalog service provider in the correct zone. + // + + try { + const auto ec = chl_delay_rule_tag_clear(*_comm, _input->rule_id); + + if (ec < 0) { + log_api::error("{}: chl_delay_rule_tag_clear failed with error code [{}].", __func__, ec); + } + + return ec; + } + catch (const irods::exception& e) { + log_api::error("{}: {}", __func__, e.client_display_what()); + return e.code(); // NOLINT(bugprone-narrowing-conversions, cppcoreguidelines-narrowing-conversions) + } + catch (const std::exception& e) { + log_api::error("{}: {}", __func__, e.what()); + return SYS_LIBRARY_ERROR; + } +} // rs_delay_rule_tag_clear diff --git a/server/control_plane/CMakeLists.txt b/server/control_plane/CMakeLists.txt deleted file mode 100644 index 11db6cb7f7..0000000000 --- a/server/control_plane/CMakeLists.txt +++ /dev/null @@ -1,53 +0,0 @@ -add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/irods/" - COMMAND "${CMAKE_COMMAND}" -E make_directory "${CMAKE_CURRENT_BINARY_DIR}/include/irods/" -) - -add_custom_command( - OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/include/irods/server_control_plane_command.hpp" - COMMAND "${CMAKE_COMMAND}" -E env LD_LIBRARY_PATH="${IRODS_EXTERNALS_FULLPATH_BOOST}/lib:${IRODS_EXTERNALS_FULLPATH_CLANG_RUNTIME}/lib:$ENV{LD_LIBRARY_PATH}" "${IRODS_EXTERNALS_FULLPATH_AVRO}/bin/avrogencpp" -n irods -o "${CMAKE_CURRENT_BINARY_DIR}/include/irods/server_control_plane_command.hpp" -i "${CMAKE_IRODS_SOURCE_DIR}/schemas/messaging/v1/server_control_plane_command.json" - MAIN_DEPENDENCY "${CMAKE_IRODS_SOURCE_DIR}/schemas/messaging/v1/server_control_plane_command.json" - DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/irods/" -) - -add_custom_target( - irods_server_control_plane_command_hpp - ALL - DEPENDS "${CMAKE_CURRENT_BINARY_DIR}/include/irods/server_control_plane_command.hpp" -) - -add_dependencies(all-server irods_server_control_plane_command_hpp) -install( - FILES - "${CMAKE_CURRENT_BINARY_DIR}/include/irods/server_control_plane_command.hpp" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/irods" - COMPONENT ${IRODS_PACKAGE_COMPONENT_DEVELOPMENT_NAME} -) - -add_library( - irods_server_control_plane - INTERFACE -) -target_link_libraries( - irods_server_control_plane - INTERFACE - irods_common -) -target_include_directories( - irods_server_control_plane - INTERFACE - "$" - "$" - "$" -) -add_dependencies( - irods_server_control_plane - irods_server_control_plane_command_hpp -) - -install( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/irods_server_control_plane.hpp" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/irods" - COMPONENT ${IRODS_PACKAGE_COMPONENT_DEVELOPMENT_NAME} -) diff --git a/server/control_plane/include/irods/irods_server_control_plane.hpp b/server/control_plane/include/irods/irods_server_control_plane.hpp deleted file mode 100644 index ec0d16b49b..0000000000 --- a/server/control_plane/include/irods/irods_server_control_plane.hpp +++ /dev/null @@ -1,155 +0,0 @@ -#ifndef IRODS_SERVER_CONTROL_PLANE_HPP -#define IRODS_SERVER_CONTROL_PLANE_HPP - -/// \file - -#include "irods/irods_lookup_table.hpp" -#include "irods/server_control_plane_command.hpp" - -#include -#include -#include - -#include -#include - -namespace irods -{ - inline const std::string SERVER_CONTROL_OPTION_KW( "server_control_option" ); - inline const std::string SERVER_CONTROL_HOST_KW( "server_control_host" ); - inline const std::string SERVER_CONTROL_FORCE_AFTER_KW( "server_control_force_after" ); - inline const std::string SERVER_CONTROL_WAIT_FOREVER_KW( "server_control_wait_forever" ); - - inline const std::string SERVER_CONTROL_SHUTDOWN( "server_control_shutdown" ); - inline const std::string SERVER_CONTROL_PAUSE( "server_control_pause" ); - inline const std::string SERVER_CONTROL_RESUME( "server_control_resume" ); - inline const std::string SERVER_CONTROL_STATUS( "server_control_status" ); - inline const std::string SERVER_CONTROL_PING( "server_control_ping" ); - inline const std::string SERVER_CONTROL_RELOAD("server_control_reload"); - - inline const std::string SERVER_CONTROL_ALL_OPT( "all" ); - inline const std::string SERVER_CONTROL_HOSTS_OPT( "hosts" ); - inline const std::string SERVER_CONTROL_SUCCESS( "server_control_success" ); - - inline const std::string SERVER_PAUSED_ERROR( "The server is Paused, resume before issuing any other commands" ); - - // this is a hand-chosen polling time for the control plane - inline const std::size_t SERVER_CONTROL_POLLING_TIME_MILLI_SEC = 500; - - // derived from above - used to wait for the server to shut down or resume - inline const std::size_t SERVER_CONTROL_FWD_SLEEP_TIME_MILLI_SEC = SERVER_CONTROL_POLLING_TIME_MILLI_SEC / 4.0; - - class server_control_executor - { - public: - server_control_executor(const std::string&, std::atomic&); // port property, accepting requests flag - - server_control_executor(const server_control_executor&) = delete; - server_control_executor& operator=(const server_control_executor&) = delete; - - // @brief callable operator for thread work - void operator()(); - - private: - using ctrl_func_t = boost::function; - using host_list_t = std::vector; - - error process_operation( - const zmq::message_t&, // incoming msg - std::string& ); // outgoing text - - error decrypt_incoming_command( - const zmq::message_t&, // incoming msg - irods::control_plane_command& ); // incoming command - - error extract_command_parameters( - const irods::control_plane_command&, // incoming command - std::string&, // command name - std::string&, // command option - std::string&, // wait option - size_t&, // wait time in seconds - host_list_t& ); // hostnames - - error perform_operation( - const std::string&, // command - const std::string&, // command option - const std::string&, // wait option - const size_t&, // wait seconds - const host_list_t&, // irods hostnames - std::string& ); // output - - error process_host_list( - const std::string&, // command - const std::string&, // wait option - const size_t&, // wait time in seconds - const host_list_t&, // hostnames - std::string& ); // outgoing text - - error validate_host_list( - const host_list_t&, // irods hostnames - const host_list_t&, // incoming hostnames - host_list_t& ); // valid hostnames - - error get_resource_host_names( - host_list_t& ); // fetched hostnames - - error notify_provider_and_local_servers_preop( - const std::string&, // command - const std::string&, // command option - const std::string&, // wait option - const size_t&, // wait seconds - const host_list_t&, // irods hostnames - std::string& ); // output - - error notify_provider_and_local_servers_postop( - const std::string&, // command - const std::string&, // command option - const std::string&, // wait option - const size_t&, // wait seconds - const host_list_t&, // irods hostnames - std::string& ); // output - - error forward_command( - const std::string&, // command name - const std::string&, // host - const std::string&, // port keyword - const std::string&, // wait option - const size_t&, // wait seconds - std::string& ); // output - - bool is_host_in_list( - const std::string&, // host name in question - const host_list_t& ); // list of candidates - - const std::string port_prop_; - std::unordered_map op_map_; - std::string local_server_hostname_; - std::string provider_hostname_; - std::atomic& is_accepting_requests_; - }; // class server_control_executor - - class server_control_plane - { - public: - // @brief default constructor taking the port property and a flag that signals - // whether the control plane is accepting requests. The boolean is set by the - // control plane. - server_control_plane(const std::string&, std::atomic&); - - server_control_plane(const server_control_plane&) = delete; - server_control_plane& operator=(const server_control_plane&) = delete; - - // @brief destructor - ~server_control_plane(); - - private: - // @brief functor which manages the control - server_control_executor control_executor_; - - // @brief thread which manages the control loop - boost::thread control_thread_; - }; // server_control_plane -} // namespace irods - -#endif // IRODS_SERVER_CONTROL_PLANE_HPP - diff --git a/server/core/CMakeLists.txt b/server/core/CMakeLists.txt index 2160268c30..43a24cd274 100644 --- a/server/core/CMakeLists.txt +++ b/server/core/CMakeLists.txt @@ -50,9 +50,7 @@ add_library( "${CMAKE_CURRENT_SOURCE_DIR}/src/objMetaOpr.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/physPath.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/plugin_lifetime_manager.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/procLog.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/replication_utilities.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/rodsAgent.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rodsConnect.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rsApiHandler.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/src/rsIcatOpr.cpp" @@ -167,10 +165,8 @@ install( "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/physPath.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/plugin_lifetime_manager.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/process_manager.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/procLog.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/replication_utilities.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/resource.hpp" - "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rodsAgent.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rodsConnect.h" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rsApiHandler.hpp" "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rsGlobalExtern.hpp" diff --git a/server/core/include/irods/agent_globals.hpp b/server/core/include/irods/agent_globals.hpp new file mode 100644 index 0000000000..23e615a492 --- /dev/null +++ b/server/core/include/irods/agent_globals.hpp @@ -0,0 +1,26 @@ +#ifndef IRODS_AGENT_GLOBALS_HPP +#define IRODS_AGENT_GLOBALS_HPP + +// Globals aren't the best, but given that C++17 supports initialization +// of global variable across translation units without violating ODR, it +// is okay to define globals for the agent factory here. +// +// With that said, let's keep this to a minimum. Defining global variables +// in this file should be a last resort. + +#include + +// This global is the flag which allows agents to gracefully react to +// stop instructions from the agent factory. +// +// It is set by signal handlers defined by the agent factory and MUST NOT +// be used for anything else. +// +// Low-level systems which need to react to stop instructions should include +// this file and check this flag. +inline volatile std::sig_atomic_t g_terminate = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +// TODO +inline volatile std::sig_atomic_t g_terminate_graceful = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + +#endif // IRODS_AGENT_GLOBALS_HPP diff --git a/server/core/include/irods/initServer.hpp b/server/core/include/irods/initServer.hpp index 15749d45af..90650a01e0 100644 --- a/server/core/include/irods/initServer.hpp +++ b/server/core/include/irods/initServer.hpp @@ -14,7 +14,7 @@ int initAgent(int processType, rsComm_t* rsComm); int initHostConfigByFile(); int initRsComm(rsComm_t* rsComm); -int initRsCommWithStartupPack(rsComm_t* rsComm, startupPack_t* startupPack); +int initRsCommWithStartupPack(rsComm_t* rsComm, startupPack_t* startupPack, bool& require_cs_neg); int chkAllowedUser(const char* userName, const char* rodsZone); int setRsCommFromRodsEnv(rsComm_t* rsComm); void close_all_l1_descriptors(RsComm& _comm); diff --git a/server/core/include/irods/irods_api_calling_functions.hpp b/server/core/include/irods/irods_api_calling_functions.hpp index 02aa0539ab..eaff22f2fb 100644 --- a/server/core/include/irods/irods_api_calling_functions.hpp +++ b/server/core/include/irods/irods_api_calling_functions.hpp @@ -1057,4 +1057,18 @@ int call_genquery2_inout(irods::api_entry*, rsComm_t*, genquery2Inp_t*, char**); # define CALL_GENQUERY2_INOUT nullptr // NOLINT(cppcoreguidelines-macro-usage) #endif +#ifdef CREATE_API_TABLE_FOR_SERVER +int call_delay_rule_tag(irods::api_entry*, rsComm_t*, delayRuleTagInp_t*); +# define CALL_DELAY_RULE_TAG call_delay_rule_tag +#else +# define CALL_DELAY_RULE_TAG nullptr // NOLINT(cppcoreguidelines-macro-usage) +#endif + +#ifdef CREATE_API_TABLE_FOR_SERVER +int call_delay_rule_tag_clear(irods::api_entry*, rsComm_t*, delayRuleTagClearInp_t*); +# define CALL_DELAY_RULE_TAG_CLEAR call_delay_rule_tag_clear +#else +# define CALL_DELAY_RULE_TAG_CLEAR nullptr // NOLINT(cppcoreguidelines-macro-usage) +#endif + #endif // IRODS_API_CALLING_FUNCTIONS_HPP diff --git a/server/core/include/irods/irods_database_constants.hpp b/server/core/include/irods/irods_database_constants.hpp index 66a74e2ed9..30017f6076 100644 --- a/server/core/include/irods/irods_database_constants.hpp +++ b/server/core/include/irods/irods_database_constants.hpp @@ -118,6 +118,8 @@ namespace irods const std::string DATABASE_OP_DATA_OBJECT_FINALIZE{"database_data_object_finalize"}; const std::string DATABASE_OP_CHECK_AUTH_CREDENTIALS{"database_check_auth_credentials"}; const std::string DATABASE_OP_EXECUTE_GENQUERY2_SQL{"database_execute_genquery2_sql"}; + const std::string DATABASE_OP_DELAY_RULE_TAG{"database_delay_rule_tag"}; + const std::string DATABASE_OP_DELAY_RULE_TAG_CLEAR{"database_delay_rule_tag_clear"}; }; // namespace irods #endif // IRODS_DATABASE_CONSTANTS_HPP diff --git a/server/core/include/irods/irods_signal.hpp b/server/core/include/irods/irods_signal.hpp index 7134c652bc..03dd2f41e8 100644 --- a/server/core/include/irods/irods_signal.hpp +++ b/server/core/include/irods/irods_signal.hpp @@ -2,6 +2,7 @@ #define IRODS_SIGNAL_HPP #include +#include namespace irods { @@ -11,16 +12,17 @@ namespace irods /// \since 4.3.0 extern const char* const STACKTRACE_NOT_READY_FOR_LOGGING_SUFFIX; - /// std::string containing the path of the directory where stacktraces are - /// stored. + /// Sets signal handlers for the following signals: + /// - SIGSEGV + /// - SIGABRT + /// - SIGILL + /// - SIGFPE + /// - SIGBUG /// - /// \since 4.3.0 - extern const std::string STACKTRACE_DIR; - - /// Sets signal handlers for SIGSEGV, SIGABRT, and SIGINT. + /// The logging system MUST be initialized before calling this function. /// /// \since 4.3.0 - void set_unrecoverable_signal_handlers(); + auto setup_unrecoverable_signal_handlers() -> void; } // namespace irods #endif // IRODS_SIGNAL_HPP diff --git a/server/core/include/irods/miscServerFunct.hpp b/server/core/include/irods/miscServerFunct.hpp index 0bd4200fdb..c7ddf0d79b 100644 --- a/server/core/include/irods/miscServerFunct.hpp +++ b/server/core/include/irods/miscServerFunct.hpp @@ -142,11 +142,13 @@ irods::error readStartupPack( irods::error setRECacheSaltFromEnv(); +#if 0 irods::error get_script_output_single_line( const std::string& script_language, const std::string& script_name, const std::vector& args, std::string& output ); +#endif irods::error add_global_re_params_to_kvp_for_dynpep( keyValPair_t& _kvp ); diff --git a/server/core/include/irods/procLog.h b/server/core/include/irods/procLog.h deleted file mode 100644 index 95b65e50b6..0000000000 --- a/server/core/include/irods/procLog.h +++ /dev/null @@ -1,17 +0,0 @@ -#ifndef PROC_LOG_H -#define PROC_LOG_H - -#include "irods/rodsConnect.h" - -int -initAndClearProcLog(); -int -initProcLog(); -int -logAgentProc( rsComm_t* ); -int -readProcLog( int pid, procLog_t *procLog ); -int -rmProcLog( int pid ); - -#endif //PROC_LOG_H diff --git a/server/core/include/irods/rodsAgent.hpp b/server/core/include/irods/rodsAgent.hpp deleted file mode 100644 index cca8440cf9..0000000000 --- a/server/core/include/irods/rodsAgent.hpp +++ /dev/null @@ -1,26 +0,0 @@ -/*** Copyright (c), The Regents of the University of California *** - *** For more information please refer to files in the COPYRIGHT directory ***/ - -/* rodsAgent.h - header file for rodsAgent - */ - -#ifndef RODS_AGENT_HPP -#define RODS_AGENT_HPP - -#include "irods/rods.h" -#include "irods/rcGlobalExtern.h" /* client global */ -#include "irods/rodsLog.h" -#include "irods/sockComm.h" -#include "irods/getRodsEnv.h" -#include "irods/rcConnect.h" -#include "irods/rsIcatOpr.hpp" - -#include - -#define MAX_MSG_READ_RETRY 1 -#define READ_RETRY_SLEEP_TIME 1 - -int agentMain( rsComm_t *rsComm ); -int runIrodsAgentFactory(sockaddr_un agent_addr); - -#endif /* RODS_AGENT_H */ diff --git a/server/core/src/initServer.cpp b/server/core/src/initServer.cpp index 49d935a3a5..f0c398a36e 100644 --- a/server/core/src/initServer.cpp +++ b/server/core/src/initServer.cpp @@ -3,12 +3,12 @@ #include "irods/genQuery.h" #include "irods/getRemoteZoneResc.h" #include "irods/getRescQuota.h" +#include "irods/irods_client_server_negotiation.hpp" #include "irods/irods_configuration_keywords.hpp" #include "irods/irods_stacktrace.hpp" #include "irods/miscServerFunct.hpp" #include "irods/objDesc.hpp" #include "irods/physPath.hpp" -#include "irods/procLog.h" #include "irods/rcGlobalExtern.h" #include "irods/rcMisc.h" #include "irods/resource.hpp" @@ -48,10 +48,12 @@ #include #include +#include #include #include #include #include +#include #include namespace @@ -179,6 +181,8 @@ int initServerInfo(int processType, rsComm_t* rsComm) } } + // TODO This function invokes acAclPolicy which guarantees GenQuery1 honors + // the permission model. acAclPolicy is invoked the first time _rsGenQuery is invoked. status = initZone( rsComm ); if ( status < 0 ) { rodsLog( LOG_SYS_FATAL, @@ -504,8 +508,6 @@ initZone( rsComm_t *rsComm ) { int initAgent( int processType, rsComm_t *rsComm ) { - initProcLog(); - int status = initServerInfo( 1, rsComm ); if ( status < 0 ) { rodsLog( LOG_ERROR, @@ -689,36 +691,40 @@ initRsComm( rsComm_t *rsComm ) { } int -initRsCommWithStartupPack( rsComm_t *rsComm, startupPack_t *startupPack ) { +initRsCommWithStartupPack( rsComm_t *rsComm, startupPack_t *startupPack, bool& require_cs_neg) { char *tmpStr; static char tmpStr2[LONG_NAME_LEN]; /* always use NATIVE_PROT as a client. e.g., server to server comm */ snprintf( tmpStr2, LONG_NAME_LEN, "%s=%d", IRODS_PROT, NATIVE_PROT ); putenv( tmpStr2 ); - if ( startupPack != NULL ) { + if (startupPack) { rsComm->connectCnt = startupPack->connectCnt; rsComm->irodsProt = startupPack->irodsProt; rsComm->reconnFlag = startupPack->reconnFlag; - rstrcpy( rsComm->proxyUser.userName, startupPack->proxyUser, - NAME_LEN ); + rstrcpy( rsComm->proxyUser.userName, startupPack->proxyUser, NAME_LEN ); if ( strcmp( startupPack->proxyUser, PUBLIC_USER_NAME ) == 0 ) { rsComm->proxyUser.authInfo.authFlag = PUBLIC_USER_AUTH; } - rstrcpy( rsComm->proxyUser.rodsZone, startupPack->proxyRodsZone, - NAME_LEN ); - rstrcpy( rsComm->clientUser.userName, startupPack->clientUser, - NAME_LEN ); + rstrcpy( rsComm->proxyUser.rodsZone, startupPack->proxyRodsZone, NAME_LEN ); + rstrcpy( rsComm->clientUser.userName, startupPack->clientUser, NAME_LEN ); if ( strcmp( startupPack->clientUser, PUBLIC_USER_NAME ) == 0 ) { rsComm->clientUser.authInfo.authFlag = PUBLIC_USER_AUTH; } - rstrcpy( rsComm->clientUser.rodsZone, startupPack->clientRodsZone, - NAME_LEN ); - rstrcpy( rsComm->cliVersion.relVersion, startupPack->relVersion, - NAME_LEN ); - rstrcpy( rsComm->cliVersion.apiVersion, startupPack->apiVersion, - NAME_LEN ); - rstrcpy( rsComm->option, startupPack->option, LONG_NAME_LEN ); + rstrcpy( rsComm->clientUser.rodsZone, startupPack->clientRodsZone, NAME_LEN ); + rstrcpy( rsComm->cliVersion.relVersion, startupPack->relVersion, NAME_LEN ); + rstrcpy( rsComm->cliVersion.apiVersion, startupPack->apiVersion, NAME_LEN ); + + std::string_view opt_str = startupPack->option; + const auto pos = opt_str.find(REQ_SVR_NEG); + require_cs_neg = (std::string_view::npos != pos); + if (require_cs_neg) { + const auto client_app_name = opt_str.substr(0, pos); + client_app_name.copy(rsComm->option, sizeof(RsComm::option)); + } + else { + opt_str.copy(rsComm->option, sizeof(RsComm::option)); + } } else { /* have to depend on env variable */ tmpStr = getenv( SP_NEW_SOCK ); @@ -831,6 +837,10 @@ initRsCommWithStartupPack( rsComm_t *rsComm, startupPack_t *startupPack ) { else { rstrcpy( rsComm->option, tmpStr, LONG_NAME_LEN ); } + + if (std::getenv(irods::RODS_CS_NEG)) { + require_cs_neg = true; + } } if (const auto ec = setLocalAddr(rsComm->sock, &rsComm->localAddr); ec == USER_RODS_HOSTNAME_ERR) { diff --git a/server/core/src/irods_api_calling_functions.cpp b/server/core/src/irods_api_calling_functions.cpp index 83dac108c2..2b527c94bf 100644 --- a/server/core/src/irods_api_calling_functions.cpp +++ b/server/core/src/irods_api_calling_functions.cpp @@ -1149,3 +1149,13 @@ int call_genquery2_inout(irods::api_entry* _api, rsComm_t* _comm, genquery2Inp_t { return _api->call_handler(_comm, _inp, _out); } // call_genquery2_inout + +int call_delay_rule_tag(irods::api_entry* _api, rsComm_t* _comm, delayRuleTagInp_t* _inp) +{ + return _api->call_handler(_comm, _inp); +} // call_delay_rule_tag + +int call_delay_rule_tag_clear(irods::api_entry* _api, rsComm_t* _comm, delayRuleTagClearInp_t* _inp) +{ + return _api->call_handler(_comm, _inp); +} // call_delay_rule_tag_clear diff --git a/server/core/src/irods_resource_manager.cpp b/server/core/src/irods_resource_manager.cpp index c7edfc89bb..09ca76f270 100644 --- a/server/core/src/irods_resource_manager.cpp +++ b/server/core/src/irods_resource_manager.cpp @@ -41,7 +41,7 @@ namespace irods // =-=-=-=-=-=-=- /// @brief special resource for local file system operations only const std::string LOCAL_USE_ONLY_RESOURCE( "LOCAL_USE_ONLY_RESOURCE" ); - const std::string LOCAL_USE_ONLY_RESOURCE_VAULT( + const std::string LOCAL_USE_ONLY_RESOURCE_VAULT( // TODO This does not appear to be used. (get_irods_home_directory() / "LOCAL_USE_ONLY_RESOURCE_VAULT").string()); const std::string LOCAL_USE_ONLY_RESOURCE_TYPE( "unixfilesystem" ); diff --git a/server/core/src/irods_server_negotiation.cpp b/server/core/src/irods_server_negotiation.cpp index 8776dfcc9f..f5389c31d2 100644 --- a/server/core/src/irods_server_negotiation.cpp +++ b/server/core/src/irods_server_negotiation.cpp @@ -71,22 +71,14 @@ namespace irods /// @brief function which manages the TLS and Auth negotiations with the client error client_server_negotiation_for_server( irods::network_object_ptr _ptr, - std::string& _result ) { + std::string& _result, + bool _require_cs_neg, + RsComm* _comm) // TODO Consider making this a required argument. + { // =-=-=-=-=-=-=- // manufacture an rei for the applyRule ruleExecInfo_t rei{}; - - // If issues arise from this local rsComm either from lack of information, - // such as the missing rsComm.myEnv, or environment variable mismatch, use - // the existing rsComm higher up the call stack. - rsComm_t rsComm{}; - auto comm_status{initRsCommWithStartupPack(&rsComm, nullptr)}; - - if (comm_status < 0) { - return ERROR(comm_status, "failed to initialize rsComm for rule execution information"); - } - - rei.rsComm = &rsComm; + rei.rsComm = _comm; std::string rule_result; std::list params; @@ -104,21 +96,18 @@ namespace irods // =-=-=-=-=-=-=- // check to see if a negotiation was requested - if ( !do_client_server_negotiation_for_server() ) { + if (!_require_cs_neg) { // =-=-=-=-=-=-=- // if it was not but we require SSL then error out if ( CS_NEG_REQUIRE == rule_result ) { std::stringstream msg; msg << "SSL is required by the server but not requested by the client"; return ERROR( SYS_INVALID_INPUT_PARAM, msg.str() ); - - } - else { - // =-=-=-=-=-=-=- - // a negotiation was not requested and we do not require SSL - we good - return SUCCESS(); } + // =-=-=-=-=-=-=- + // a negotiation was not requested and we do not require SSL - we good + return SUCCESS(); } // =-=-=-=-=-=-=- @@ -208,5 +197,4 @@ namespace irods msg.str() ); } // client_server_negotiation_for_server - -}; // namespace irods +} // namespace irods diff --git a/server/core/src/irods_signal.cpp b/server/core/src/irods_signal.cpp index 958b3c1487..8243158903 100644 --- a/server/core/src/irods_signal.cpp +++ b/server/core/src/irods_signal.cpp @@ -1,10 +1,13 @@ #include "irods/irods_signal.hpp" #include "irods/irods_default_paths.hpp" +#include "irods/irods_exception.hpp" #include "irods/irods_logger.hpp" +#include "irods/rodsErrorTable.h" #include +#include #include // For _POSIX_PATH_MAX #include #include @@ -17,11 +20,16 @@ #include +namespace +{ + // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) + std::array g_stacktrace_dir; +} // anonymous namespace + extern "C" void stacktrace_signal_handler(int _signal) { - timespec ts; - + timespec ts; // NOLINT(cppcoreguidelines-pro-type-member-init) if (clock_gettime(CLOCK_REALTIME, &ts) != 0) { _exit(_signal); } @@ -29,24 +37,29 @@ void stacktrace_signal_handler(int _signal) // "_POSIX_PATH_MAX" is guaranteed to be defined as 256. // This value represents the minimum path length supported by POSIX operating systems. char file_path_unprocessed[_POSIX_PATH_MAX]; - std::strncpy(file_path_unprocessed, irods::STACKTRACE_DIR.c_str(), _POSIX_PATH_MAX); + std::strncpy(file_path_unprocessed, g_stacktrace_dir.data(), _POSIX_PATH_MAX); std::strcat(file_path_unprocessed, "/"); + // At this point, "file_path_unprocessed" should have the following path: // // /stacktraces/ // + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) auto* buf_start = file_path_unprocessed + strlen(file_path_unprocessed); + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) auto* buf_end = file_path_unprocessed + sizeof(file_path_unprocessed); if (auto [sec_end, ec] = std::to_chars(buf_start, buf_end, ts.tv_sec); std::errc{} == ec) { *sec_end = '.'; + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) if (auto [msec_end, ec] = std::to_chars(sec_end + 1, buf_end, ts.tv_nsec / 1'000'000); std::errc{} == ec) { *msec_end = '.'; + // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-pointer-arithmetic) if (auto [pid_end, ec] = std::to_chars(msec_end + 1, buf_end, getpid()); std::errc{} == ec) { // Terminate the path string. The filename should have the following format: // @@ -78,23 +91,36 @@ namespace irods { const char* const STACKTRACE_NOT_READY_FOR_LOGGING_SUFFIX = ".not_ready_for_logging"; - const std::string STACKTRACE_DIR(irods::get_irods_stacktrace_directory().string()); - const std::string DISABLE_CRASH_SIGNAL_HANDLERS_ENV_VAR("IRODS_DISABLE_CRASH_SIGNAL_HANDLERS"); + auto get_stacktrace_directory_path() -> std::string_view + { + return g_stacktrace_dir.data(); + } // get_stacktrace_directory_path - void set_unrecoverable_signal_handlers() + auto setup_unrecoverable_signal_handlers() -> void { using log_server = irods::experimental::log::server; - log_server::debug("Setting stacktrace dump signal handler for process [{}].", getpid()); - log_server::debug("Stacktraces will be dumped to [{}].", irods::STACKTRACE_DIR); + std::fill(std::begin(g_stacktrace_dir), std::end(g_stacktrace_dir), 0); + const auto path = get_irods_stacktrace_directory().string(); + if (path.size() >= g_stacktrace_dir.size()) { + constexpr const auto* msg = "Stacktrace directory path cannot exceed buffer size [{}]"; + THROW(CONFIGURATION_ERROR, fmt::format(msg, g_stacktrace_dir.size())); + } + path.copy(g_stacktrace_dir.data(), g_stacktrace_dir.size()); + log_server::debug("{}: Stacktraces will be dumped to [{}].", __func__, g_stacktrace_dir.data()); + + log_server::debug("{}: Setting up unrecoverable stacktrace signal handlers.", __func__); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) struct sigaction action; action.sa_flags = 0; sigemptyset(&action.sa_mask); + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) action.sa_handler = stacktrace_signal_handler; - const char *env_var_val = std::getenv(DISABLE_CRASH_SIGNAL_HANDLERS_ENV_VAR.c_str()); - if (env_var_val == nullptr || (std::strncmp(env_var_val, "0", 2) == 0)) { + constexpr const char* env_var = "IRODS_DISABLE_CRASH_SIGNAL_HANDLERS"; + const char *env_var_val = std::getenv(env_var); // NOLINT(concurrency-mt-unsafe) + if (nullptr == env_var_val || (std::strncmp(env_var_val, "0", sizeof("0")) == 0)) { sigaction(SIGSEGV, &action, nullptr); sigaction(SIGABRT, &action, nullptr); sigaction(SIGILL, &action, nullptr); @@ -102,9 +128,7 @@ namespace irods sigaction(SIGBUS, &action, nullptr); } else { - log_server::debug("{} is set; crash signal handlers disabled.", DISABLE_CRASH_SIGNAL_HANDLERS_ENV_VAR); + log_server::warn("{}: {} is set; crash signal handlers disabled.", __func__, env_var); } - - sigaction(SIGINT, &action, nullptr); } // set_unrecoverable_signal_handlers } // namespace irods diff --git a/server/core/src/miscServerFunct.cpp b/server/core/src/miscServerFunct.cpp index 6ef24e7eec..e16cbf00f9 100644 --- a/server/core/src/miscServerFunct.cpp +++ b/server/core/src/miscServerFunct.cpp @@ -51,10 +51,12 @@ #include #include +#if 0 #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wc++11-narrowing" #include #pragma GCC diagnostic pop +#endif #include @@ -2668,6 +2670,7 @@ readStartupPack( if ( strcmp(myHeader.type, RODS_HEARTBEAT_T) == 0 ) { *startupPack = static_cast(malloc(sizeof(**startupPack))); + std::memset(*startupPack, 0, sizeof(startupPack)); snprintf((*startupPack)->option, sizeof((*startupPack)->option), "%s", RODS_HEARTBEAT_T); return SUCCESS(); } @@ -2989,6 +2992,7 @@ irods::error setRECacheSaltFromEnv() return SUCCESS(); } // setRECacheSaltFromEnv +#if 0 irods::error get_script_output_single_line( const std::string& script_language, const std::string& script_name, @@ -3002,6 +3006,17 @@ irods::error get_script_output_single_line( return ERROR(RESOLUTION_ERROR, fmt::format("Could not resolve {} to an executable", script_language)); } + // TODO Consider removing entirely. Currently, only the following things invoke this function: + // - server/api/src/rsServerReport.cpp:257: ret = get_script_output_single_line( "python3", "system_identification.py", args, os_distribution_name ); + // - server/api/src/rsServerReport.cpp:268: ret = get_script_output_single_line( "python3", "system_identification.py", args, os_distribution_version ); + // - server/main_server/src/main_server_control_plane.cpp:328: irods::error ret = get_script_output_single_line("python3", "pid_age.py", args, pid_age); + // + // Have to support zone reports. + // The use of system_identification.py can be replaced with parsing of /etc/os-release + // and/or the POSIX uname() function. uname() will not return the same information, but + // maybe that's okay? Parsing /etc/os-release would give us more detailed OS information. + // + // The control plane is going away, so there's no need for pid_age.py to exist. auto script_path = fmt::format("{}/scripts/{}", irods::get_irods_home_directory().c_str(), script_name); if (!std::filesystem::exists(script_path)) { return ERROR(RESOLUTION_ERROR, fmt::format("Script file not found! {}", script_path)); @@ -3052,6 +3067,7 @@ irods::error get_script_output_single_line( return ERROR(SYS_INTERNAL_ERR, e.what()); } } // get_script_output_single_line +#endif irods::error add_global_re_params_to_kvp_for_dynpep( keyValPair_t& _kvp ) { diff --git a/server/core/src/procLog.cpp b/server/core/src/procLog.cpp deleted file mode 100644 index 34e82e3af5..0000000000 --- a/server/core/src/procLog.cpp +++ /dev/null @@ -1,161 +0,0 @@ -#include "irods/procLog.h" - -#include "irods/miscUtil.h" -#include "irods/rsGlobalExtern.hpp" -#include "irods/rodsConnect.h" -#include "irods/irods_default_paths.hpp" - -#include - -#include - -#include -#include - -int -initAndClearProcLog() { - initProcLog(); - int error_code = mkdir( ProcLogDir, DEFAULT_DIR_MODE ); - int errsv = errno; - if ( error_code != 0 && errsv != EEXIST ) { - rodsLog( LOG_ERROR, "mkdir failed in initAndClearProcLog with error code %d", error_code ); - } - rmFilesInDir( ProcLogDir ); - - return 0; -} - -int -initProcLog() { - const auto proc_log_dir = irods::get_irods_home_directory() / "log" / "proc"; - std::strncpy(ProcLogDir, proc_log_dir.c_str(), MAX_NAME_LEN); - return 0; -} - -int -logAgentProc( rsComm_t *rsComm ) { - FILE *fptr; - char procPath[MAX_NAME_LEN]; - char *remoteAddr; - char *progName; - char *clientZone, *proxyZone; - - if ( rsComm->procLogFlag == PROC_LOG_DONE ) { - return 0; - } - - if ( *rsComm->clientUser.userName == '\0' || - *rsComm->proxyUser.userName == '\0' ) { - return 0; - } - - if ( *rsComm->clientUser.rodsZone == '\0' ) { - if ( ( clientZone = getLocalZoneName() ) == NULL ) { - clientZone = "UNKNOWN"; - } - } - else { - clientZone = rsComm->clientUser.rodsZone; - } - - if ( *rsComm->proxyUser.rodsZone == '\0' ) { - if ( ( proxyZone = getLocalZoneName() ) == NULL ) { - proxyZone = "UNKNOWN"; - } - } - else { - proxyZone = rsComm->proxyUser.rodsZone; - } - - remoteAddr = inet_ntoa( rsComm->remoteAddr.sin_addr ); - if ( remoteAddr == NULL || *remoteAddr == '\0' ) { - remoteAddr = "UNKNOWN"; - } - if ( *rsComm->option == '\0' ) { - progName = "UNKNOWN"; - } - else { - progName = rsComm->option; - } - - snprintf( procPath, MAX_NAME_LEN, "%s/%-d", ProcLogDir, getpid() ); - - fptr = fopen( procPath, "w" ); - - if ( fptr == NULL ) { - rodsLog( LOG_ERROR, - "logAgentProc: Cannot open input file %s. errno = %d", - procPath, errno ); - return UNIX_FILE_OPEN_ERR - errno; - } - - fprintf( fptr, "%s %s %s %s %s %s %u\n", - rsComm->proxyUser.userName, clientZone, - rsComm->clientUser.userName, proxyZone, - progName, remoteAddr, ( unsigned int ) time( 0 ) ); - - rsComm->procLogFlag = PROC_LOG_DONE; - fclose( fptr ); - return 0; -} - -int rmProcLog(int pid) -{ - char procPath[MAX_NAME_LEN]{}; - - std::strcpy(procPath, ProcLogDir); - std::strcat(procPath, "/"); - - auto* buf_start = procPath + std::strlen(procPath); - auto* buf_end = procPath + sizeof(procPath); - - if (auto [ptr, ec] = std::to_chars(buf_start, buf_end, pid); std::errc{} == ec) { - unlink(procPath); - } - - return 0; -} - -int -readProcLog( int pid, procLog_t *procLog ) { - - if ( procLog == NULL ) { - return USER__NULL_INPUT_ERR; - } - - char procPath[MAX_NAME_LEN]; - snprintf( procPath, MAX_NAME_LEN, "%s/%-d", ProcLogDir, pid ); - - std::fstream procStream( procPath, std::ios::in ); - std::vector procTokens; - while ( !procStream.eof() && procTokens.size() < 7 ) { - std::string token; - procStream >> token; - procTokens.push_back( token ); - } - - if ( procTokens.size() != 7 ) { - rodsLog( LOG_ERROR, - "readProcLog: error fscanf file %s. Number of param read = %d", - procPath, procTokens.size() ); - return UNIX_FILE_READ_ERR; - } - - procLog->pid = pid; - - snprintf( procLog->clientName, sizeof( procLog->clientName ), "%s", procTokens[0].c_str() ); - snprintf( procLog->clientZone, sizeof( procLog->clientZone ), "%s", procTokens[1].c_str() ); - snprintf( procLog->proxyName, sizeof( procLog->proxyName ), "%s", procTokens[2].c_str() ); - snprintf( procLog->proxyZone, sizeof( procLog->proxyZone ), "%s", procTokens[3].c_str() ); - snprintf( procLog->progName, sizeof( procLog->progName ), "%s", procTokens[4].c_str() ); - snprintf( procLog->remoteAddr, sizeof( procLog->remoteAddr ), "%s", procTokens[5].c_str() ); - try { - procLog->startTime = boost::lexical_cast( procTokens[6].c_str() ); - } - catch ( ... ) { - rodsLog( LOG_ERROR, "Could not convert %s to unsigned int.", procTokens[6].c_str() ); - return INVALID_LEXICAL_CAST; - } - - return 0; -} diff --git a/server/core/src/rodsAgent.cpp b/server/core/src/rodsAgent.cpp deleted file mode 100644 index 948d4af9de..0000000000 --- a/server/core/src/rodsAgent.cpp +++ /dev/null @@ -1,896 +0,0 @@ -#include "irods/rodsAgent.hpp" - -#include "irods/icatHighLevelRoutines.hpp" -#include "irods/initServer.hpp" -#include "irods/irods_at_scope_exit.hpp" -#include "irods/irods_auth_constants.hpp" -#include "irods/irods_auth_factory.hpp" -#include "irods/irods_auth_manager.hpp" -#include "irods/irods_auth_object.hpp" -#include "irods/irods_auth_plugin.hpp" -#include "irods/irods_client_api_table.hpp" -#include "irods/irods_client_server_negotiation.hpp" -#include "irods/irods_dynamic_cast.hpp" -#include "irods/irods_environment_properties.hpp" -#include "irods/irods_logger.hpp" -#include "irods/irods_network_factory.hpp" -#include "irods/irods_pack_table.hpp" -#include "irods/irods_re_plugin.hpp" -#include "irods/irods_re_serialization.hpp" -#include "irods/irods_server_api_table.hpp" -#include "irods/irods_server_properties.hpp" -#include "irods/irods_server_state.hpp" -#include "irods/irods_signal.hpp" -#include "irods/irods_socket_information.hpp" -#include "irods/irods_threads.hpp" -#include "irods/miscServerFunct.hpp" -#include "irods/plugin_lifetime_manager.hpp" -#include "irods/procLog.h" -#include "irods/reconstants.hpp" -#include "irods/replica_access_table.hpp" -#include "irods/replica_state_table.hpp" -#include "irods/rodsErrorTable.h" -#include "irods/rsApiHandler.hpp" -#include "irods/server_utilities.hpp" -#include "irods/sockCommNetworkInterface.hpp" -#include "irods/sslSockComm.h" -#include "irods/version.hpp" - -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) -# include -#endif - -namespace log_ns = irods::experimental::log; - -// clang-format off -using log_agent_factory = irods::experimental::log::agent_factory; -using log_agent = irods::experimental::log::agent; -// clang-format on - -// NOLINTNEXTLINE(modernize-use-trailing-return-type) -ssize_t receiveSocketFromSocket(int readFd, int* socket) -{ - struct msghdr msg; // NOLINT(cppcoreguidelines-pro-type-member-init) - std::memset(&msg, 0, sizeof(struct msghdr)); - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) - std::array message_buf{}; - struct iovec io = {.iov_base = message_buf.data(), .iov_len = message_buf.size()}; - msg.msg_iov = &io; - msg.msg_iovlen = 1; - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) - std::array control_buf{}; - msg.msg_control = control_buf.data(); - msg.msg_controllen = control_buf.size(); - - ssize_t n = recvmsg(readFd, &msg, MSG_WAITALL); - if (n <= 0) { - return n; - } - - cmsghdr* cmptr = CMSG_FIRSTHDR(&msg); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) - unsigned char* data = CMSG_DATA(cmptr); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - int theSocket = *((int*) data); // NOLINT(google-readability-casting, cppcoreguidelines-pro-type-cstyle-cast) - *socket = theSocket; - - return n; -} // receiveSocketFromSocket - -// NOLINTNEXTLINE(modernize-use-trailing-return-type) -int receiveDataFromServer(int conn_tmp_socket) -{ - ssize_t num_bytes{}; - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init, cppcoreguidelines-avoid-magic-numbers) - std::array in_buf{}; // NOLINT(readability-magic-numbers) - bool data_complete = false; - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init, cppcoreguidelines-avoid-magic-numbers) - std::array ack_buffer; // NOLINT(readability-magic-numbers) - std::snprintf(ack_buffer.data(), ack_buffer.size(), "OK"); // NOLINT(cppcoreguidelines-pro-type-vararg) - - while (!data_complete) { - std::memset(in_buf.data(), 0, in_buf.size()); - num_bytes = recv(conn_tmp_socket, in_buf.data(), in_buf.size(), 0); - - if (num_bytes < 0) { - // NOLINTNEXTLINE(concurrency-mt-unsafe) - log_agent_factory::error("Error receiving data from rodsServer, errno = [{}][{}]", errno, strerror(errno)); - return SYS_SOCK_READ_ERR; - } - - if (num_bytes == 0) { - log_agent_factory::error("Received 0 bytes from rodsServer"); - return SYS_SOCK_READ_ERR; - } - - char* tokenized_strings = std::strtok(in_buf.data(), ";"); // NOLINT(concurrency-mt-unsafe) - - while (tokenized_strings != nullptr) { - const std::string_view tmpStr = tokenized_strings; - - if (tmpStr == "end_of_vars") { - data_complete = true; - - // Send acknowledgement that all data has been received - num_bytes = send(conn_tmp_socket, ack_buffer.data(), std::strlen(ack_buffer.data()) + 1, 0); - if (num_bytes < 0) { - constexpr const char* msg = "Error sending acknowledgment to rodsServer, errno = [{}][{}]"; - // NOLINTNEXTLINE(concurrency-mt-unsafe) - log_agent_factory::error(msg, errno, strerror(errno)); - return SYS_SOCK_READ_ERR; - } - - break; - } - - const auto pos = tmpStr.find('='); - - if (pos == std::string_view::npos) { - log_agent_factory::error( - "{}: Malformed payload: please check the client's settings for invalid characters.", __func__); - return SYS_AGENT_INIT_ERR; - } - - const auto lhs = tmpStr.substr(0, pos); - const auto rhs = tmpStr.substr(pos + 1, tmpStr.size()); - - // NOLINTNEXTLINE(concurrency-mt-unsafe) - if (setenv(std::string{lhs}.c_str(), std::string{rhs}.c_str(), 1) < 0) { - constexpr const char* msg_fmt = "setenv([{}], [{}], 1) failed with errno = [{}][{}]"; - // NOLINTNEXTLINE(concurrency-mt-unsafe) - const auto msg = fmt::format(msg_fmt, lhs, rhs, errno, strerror(errno)); - log_agent_factory::error(ERROR(SYS_INTERNAL_ERR, msg).result()); - } - - // NOLINTNEXTLINE(concurrency-mt-unsafe) - tokenized_strings = std::strtok(nullptr, ";"); - } - } - - int newSocket{}; - num_bytes = receiveSocketFromSocket(conn_tmp_socket, &newSocket); - if (num_bytes < 0) { - // NOLINTNEXTLINE(concurrency-mt-unsafe) - log_agent_factory::error("Error receiving socket from rodsServer, errno = [{}]", errno, strerror(errno)); - return SYS_SOCK_READ_ERR; - } - - if (num_bytes == 0) { - log_agent_factory::error("Received 0 bytes from rodsServer"); - return SYS_SOCK_READ_ERR; - } - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init, cppcoreguidelines-avoid-magic-numbers) - std::array socket_buf; // NOLINT(readability-magic-numbers) - std::snprintf(socket_buf.data(), socket_buf.size(), "%d", newSocket); // NOLINT(cppcoreguidelines-pro-type-vararg) - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) - unsigned int len = std::snprintf(ack_buffer.data(), ack_buffer.size(), "%d", getpid()); - num_bytes = send(conn_tmp_socket, ack_buffer.data(), len, 0); - if (num_bytes < 0) { - // NOLINTNEXTLINE(concurrency-mt-unsafe) - log_agent_factory::error("Error sending agent pid to rodsServer, errno = [{}]", errno, strerror(errno)); - return SYS_SOCK_READ_ERR; - } - - if (setenv(SP_NEW_SOCK, socket_buf.data(), 1) < 0) { // NOLINT(concurrency-mt-unsafe) - constexpr const char* msg_fmt = "setenv([{}], [{}], 1) failed with errno = [{}][{}]"; - // NOLINTNEXTLINE(concurrency-mt-unsafe) - const auto msg = fmt::format(msg_fmt, SP_NEW_SOCK, socket_buf.data(), errno, strerror(errno)); - log_agent_factory::error(ERROR(SYS_INTERNAL_ERR, msg).result()); - } - - const auto status = close(conn_tmp_socket); - if (status < 0) { - // NOLINTNEXTLINE(concurrency-mt-unsafe) - log_agent_factory::error("close(conn_tmp_socket) failed with errno = [{}]: {}", errno, strerror(errno)); - } - - return status; -} // receiveDataFromServer - -void cleanup() -{ - std::string svc_role; - irods::error ret = get_catalog_service_role(svc_role); - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - } - - if (INITIAL_DONE == InitialState) { - close_all_l1_descriptors(*ThisComm); - - // This agent's PID must be erased from all replica access table entries as it will soon no longer exist. - irods::experimental::replica_access_table::erase_pid(getpid()); - - irods::replica_state_table::deinit(); - - disconnectAllSvrToSvrConn(); - } - - if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == svc_role) { - disconnectRcat(); - } - - irods::re_plugin_globals->global_re_mgr.call_stop_operations(); -} // cleanup - -void cleanupAndExit(int status) -{ - cleanup(); - - if (status >= 0) { - std::exit(0); // NOLINT(concurrency-mt-unsafe) - } - - std::exit(1); // NOLINT(concurrency-mt-unsafe) -} // cleanupAndExit - -void irodsAgentSignalExit([[maybe_unused]] int _signal) -{ - int agent_pid{}; - int agent_status{}; - - while ((agent_pid = waitpid(-1, &agent_status, WNOHANG)) > 0) { - rmProcLog(agent_pid); - } - -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - // Calling this function is likely not async-signal-safe, but that's okay because - // the code has been compiled with Address Sanitizer enabled. For that reason, we - // can assume that the binary is not running in a production environment. - __lsan_do_leak_check(); -#endif - - _exit(_signal); -} - -void reap_terminated_agents() -{ - int agent_pid{}; - int agent_status{}; - - while ((agent_pid = waitpid(-1, &agent_status, WNOHANG)) > 0) { - log_agent_factory::trace("Reaped agent [{}] ...", agent_pid); - - if (WIFEXITED(agent_status)) { // NOLINT(hicpp-signed-bitwise) - const int exit_status = WEXITSTATUS(agent_status); // NOLINT(hicpp-signed-bitwise) - if (exit_status != 0) { - log_agent_factory::error("Agent process [{}] exited with status [{}].", agent_pid, exit_status); - } - else { - log_agent_factory::debug("Agent process [{}] exited with status [{}].", agent_pid, exit_status); - } - } - else if (WIFSIGNALED(agent_status)) { // NOLINT(hicpp-signed-bitwise) - constexpr const char* msg = "Agent process [{}] terminated by signal [{}]."; - // NOLINTNEXTLINE(hicpp-signed-bitwise) - log_agent_factory::error(msg, agent_pid, WTERMSIG(agent_status)); - } - else { - log_agent_factory::error( - "Agent process [{}] terminated with unusual status [{}].", agent_pid, agent_status); - } - - rmProcLog(agent_pid); - - log_agent_factory::trace("Removing agent PID [{}] from replica access table ...", agent_pid); - irods::experimental::replica_access_table::erase_pid(agent_pid); - } -} // reap_terminated_agents - -void set_eviction_age_for_dns_and_hostname_caches() -{ - using key_path_t = irods::configuration_parser::key_path_t; - - // Update the eviction age for DNS cache entries. - irods::set_server_property( - key_path_t{irods::KW_CFG_ADVANCED_SETTINGS, irods::KW_CFG_DNS_CACHE, irods::KW_CFG_EVICTION_AGE_IN_SECONDS}, - irods::get_dns_cache_eviction_age()); - - // Update the eviction age for hostname cache entries. - irods::set_server_property( - key_path_t{ - irods::KW_CFG_ADVANCED_SETTINGS, irods::KW_CFG_HOSTNAME_CACHE, irods::KW_CFG_EVICTION_AGE_IN_SECONDS}, - irods::get_hostname_cache_eviction_age()); -} // set_eviction_age_for_dns_and_hostname_caches - -void set_log_levels_for_all_log_categories() -{ - log_ns::agent::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_AGENT)); - log_ns::legacy::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_LEGACY)); - log_ns::resource::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_RESOURCE)); - log_ns::genquery2::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_GENQUERY2)); - log_ns::database::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_DATABASE)); - log_ns::authentication::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_AUTHENTICATION)); - log_ns::api::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_API)); - log_ns::microservice::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_MICROSERVICE)); - log_ns::network::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_NETWORK)); - log_ns::rule_engine::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_RULE_ENGINE)); - log_ns::sql::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_SQL)); -} // set_log_levels_for_all_log_categories - -void setup_signal_handlers() -{ - signal(SIGINT, irodsAgentSignalExit); - signal(SIGHUP, irodsAgentSignalExit); - signal(SIGTERM, irodsAgentSignalExit); - signal(SIGCHLD, SIG_DFL); // Setting SIGCHLD to SIG_IGN is not portable. - signal(SIGUSR1, irodsAgentSignalExit); - signal(SIGPIPE, SIG_IGN); - - irods::set_unrecoverable_signal_handlers(); -} // setup_signal_handlers - -// NOLINTNEXTLINE(modernize-use-trailing-return-type) -int setup_unix_domain_socket_for_listening(sockaddr_un _socket_addr) -{ - const auto sfd = socket(AF_UNIX, SOCK_STREAM, 0); - - if (sfd < 0) { - // NOLINTNEXTLINE(concurrency-mt-unsafe) - log_agent_factory::error("Unable to create socket in runIrodsAgent, errno = [{}]: {}", errno, strerror(errno)); - return SYS_SOCK_OPEN_ERR; - } - - // Delete socket if it already exists. - unlink(_socket_addr.sun_path); // NOLINT(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - - // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) - if (bind(sfd, reinterpret_cast(&_socket_addr), sizeof(sockaddr_un)) < 0) { - // NOLINTNEXTLINE(concurrency-mt-unsafe) - log_agent_factory::error("Unable to bind socket in runIrodsAgent, errno [{}]: {}", errno, strerror(errno)); - return SYS_SOCK_BIND_ERR; - } - - if (listen(sfd, 5) < 0) { // NOLINT(cppcoreguidelines-avoid-magic-numbers, readability-magic-numbers) - constexpr const char* msg = "Unable to set up socket for listening in runIrodsAgent, errno [{}]: {}"; - log_agent_factory::error(msg, errno, strerror(errno)); // NOLINT(concurrency-mt-unsafe) - return SYS_SOCK_LISTEN_ERR; - } - - return sfd; -} // setup_unix_domain_socket_for_listening - -int runIrodsAgentFactory(sockaddr_un agent_addr) -{ - namespace log = irods::experimental::log; - - log_ns::set_server_type("agent_factory"); - - irods::server_properties::instance().capture(); - log_agent_factory::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_AGENT_FACTORY)); - - log_agent_factory::info("Initializing agent factory ..."); - - setup_signal_handlers(); - - initProcLog(); - - const auto listen_socket = setup_unix_domain_socket_for_listening(agent_addr); - if (listen_socket < 0) { - return listen_socket; - } - - struct sockaddr_un client_addr; - - const auto client_socket = [listen_socket, &client_addr] { - unsigned int len = sizeof(sockaddr_un); - return accept(listen_socket, reinterpret_cast(&client_addr), &len); - }(); - - if (client_socket < 0) { - constexpr const char* msg = "Failed to accept client socket in runIrodsAgent, errno [{}]: {}"; - log_agent_factory::error(msg, errno, strerror(errno)); // NOLINT(concurrency-mt-unsafe) - return SYS_SOCK_ACCEPT_ERR; - } - - int conn_tmp_socket; - - while (true) { - reap_terminated_agents(); - - fd_set read_socket; - FD_ZERO(&read_socket); - FD_SET(client_socket, &read_socket); - - struct timeval time_out; - time_out.tv_sec = 0; - time_out.tv_usec = 30 * 1000; - const int ready = select(client_socket + 1, &read_socket, nullptr, nullptr, &time_out); - - // Check the ready socket - if (-1 == ready) { - if (EINTR == errno) { - // Caught a signal, return to the select() call - log_agent_factory::debug("select() was interrupted in the agent factory process, continuing ..."); - continue; - } - - // select() failed, quit - log_agent_factory::error("select() failed with errno = [{}]: {}", errno, strerror(errno)); - return SYS_SOCK_SELECT_ERR; - } - - if (ready == 0) { - continue; - } - - // select returned, attempt to receive data - // If 0 bytes are received, socket has been closed - // If a socket address is on the line, create it and fork a child process - char in_buf[1024]{}; - const ssize_t bytes_received = recv(client_socket, in_buf, sizeof(in_buf), 0); - - if (-1 == bytes_received) { - log_agent_factory::error("Error receiving data from rodsServer, errno = [{}]: {}", errno, strerror(errno)); - return SYS_SOCK_READ_ERR; - } - - if (0 == bytes_received) { - log_agent_factory::info("The rodsServer socket peer has shut down."); - return 0; - } - - // Assume that we have received valid data over the socket connection. - // Set up the temporary (per-agent) sockets. - sockaddr_un tmp_socket_addr{}; - tmp_socket_addr.sun_family = AF_UNIX; - std::strncpy(tmp_socket_addr.sun_path, in_buf, sizeof(tmp_socket_addr.sun_path)); - unsigned int len = sizeof(tmp_socket_addr); - - const auto listen_tmp_socket = socket(AF_UNIX, SOCK_STREAM, 0); - - // Delete socket if it already exists. - unlink(tmp_socket_addr.sun_path); - - if (bind(listen_tmp_socket, (struct sockaddr*) &tmp_socket_addr, len) == -1) { - constexpr auto ec = SYS_SOCK_BIND_ERR; - log_agent_factory::error(ERROR(ec, "Unable to bind socket in receiveDataFromServer").result()); - return ec; - } - - if (listen(listen_tmp_socket, 5) == -1) { - constexpr auto ec = SYS_SOCK_LISTEN_ERR; - constexpr const char* msg = "Failed to set up socket for listening in receiveDataFromServer"; - log_agent_factory::error(ERROR(ec, msg).result()); - return ec; - } - - // Send acknowledgement that socket has been created. - constexpr const char ack_buffer[] = "OK"; - const auto bytes_sent = send(client_socket, ack_buffer, sizeof(ack_buffer), 0); - if (bytes_sent < 0) { - constexpr const char* msg = "[{}] - Error sending acknowledgment to rodsServer, errno = [{}][{}]"; - log_agent_factory::error(msg, __func__, errno, strerror(errno)); - return SYS_SOCK_READ_ERR; - } - - // Wait for connection message from main server. - std::memset(in_buf, 0, sizeof(in_buf)); - recv(client_socket, in_buf, sizeof(in_buf), 0); - if (std::strncmp(in_buf, "connection_successful", sizeof(in_buf)) != 0) { - constexpr const char* msg = "[{}:{}] - received failure message in connecting to socket from server"; - log_agent_factory::error(msg, __func__, __LINE__); - - if (close(listen_tmp_socket) < 0) { - log_agent_factory::error( - "close(listen_tmp_socket) failed with errno = [{}]: {}", errno, strerror(errno)); - } - - continue; - } - - conn_tmp_socket = accept(listen_tmp_socket, (struct sockaddr*) &tmp_socket_addr, &len); - if (-1 == conn_tmp_socket) { - constexpr auto ec = SYS_SOCK_ACCEPT_ERR; - log_agent_factory::error(ERROR(ec, "Failed to accept client socket in receiveDataFromServer").result()); - return ec; - } - - // - // Data is ready on conn_socket, fork a child process to handle it. - // - - log_agent_factory::trace("Spawning agent to handle request ..."); - - const auto agent_pid = fork(); - - // This socket will not be used by the agent factory or agent, so close it. - close(listen_tmp_socket); - - if (agent_pid == 0) { - // This is the child process. - // Agent logic starts outside of the while-loop. - break; - } - else if (agent_pid > 0) { - // This is the parent process. - // Clean up and prepare to fork more agents upon request. - close(conn_tmp_socket); - } - else if (agent_pid < 0) { - log_agent_factory::critical("Agent factory failed to fork agent. Shutting down agent factory ..."); - - close(client_socket); - close(listen_socket); - - return SYS_FORK_ERROR; - } - } // Agent factory main loop. - - // - // This is where the agent logic actually begins. - // - - int status{}; - - try { - log_ns::set_server_type("agent"); - - // Reload irods_environment.json and server_config.json for the newly forked agent process. - irods::environment_properties::instance().capture(); - irods::server_properties::instance().capture(); - - log_agent::trace("Agent forked. Initializing ..."); - - close(listen_socket); - - // Restore signal dispositions for agents. - std::signal(SIGABRT, SIG_DFL); - std::signal(SIGINT, SIG_DFL); - std::signal(SIGHUP, SIG_DFL); - std::signal(SIGTERM, SIG_DFL); - std::signal(SIGCHLD, SIG_DFL); - std::signal(SIGUSR1, SIG_DFL); - std::signal(SIGPIPE, SIG_DFL); - - status = receiveDataFromServer(conn_tmp_socket); - if (status < 0) { - log_agent::error("receiveDataFromServer failed [error_code=[{}]].", status); - } - - close(conn_tmp_socket); - - set_eviction_age_for_dns_and_hostname_caches(); - set_log_levels_for_all_log_categories(); - - if (const auto err = setRECacheSaltFromEnv(); !err.ok()) { - log_agent::error("rodsAgent::main: Failed to set RE cache mutex name\n%s", err.result()); - return SYS_INTERNAL_ERR; - } - } - catch (const irods::exception& e) { - log_agent::error("Agent initialization error: {}", e.what()); - return e.code() == -1 ? SYS_UNKNOWN_ERROR : e.code(); - } - catch (const std::exception& e) { - log_agent::error("Agent initialization error: {}", e.what()); - return SYS_LIBRARY_ERROR; - } - - RsComm rsComm{}; - - log_ns::set_error_object(&rsComm.rError); - irods::at_scope_exit release_error_stack{[] { log_ns::set_error_object(nullptr); }}; - - //std::memset(&rsComm, 0, sizeof(RsComm)); - rsComm.thread_ctx = static_cast(std::malloc(sizeof(thread_context))); - - status = initRsCommWithStartupPack(&rsComm, nullptr); - - // manufacture a network object for comms - irods::network_object_ptr net_obj; - irods::error ret = irods::network_factory(&rsComm, net_obj); - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - } - - if (status < 0) { - log_agent::error("initRsCommWithStartupPack error: [{}]", status); - sendVersion(net_obj, status, 0, nullptr, 0); - cleanupAndExit(status); - } - - irods::re_plugin_globals.reset(new irods::global_re_plugin_mgr); - irods::re_plugin_globals->global_re_mgr.call_start_operations(); - - status = getRodsEnv(&rsComm.myEnv); - - if (status < 0) { - log_agent::error("agentMain :: getRodsEnv failed"); - sendVersion(net_obj, SYS_AGENT_INIT_ERR, 0, nullptr, 0); - cleanupAndExit(status); - } - - // load server side pluggable api entries - irods::api_entry_table& RsApiTable = irods::get_server_api_table(); - irods::pack_entry_table& ApiPackTable = irods::get_pack_table(); - ret = irods::init_api_table(RsApiTable, ApiPackTable, false); - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - return 1; - } - - // load client side pluggable api entries - irods::api_entry_table& RcApiTable = irods::get_client_api_table(); - ret = irods::init_api_table(RcApiTable, ApiPackTable, false); - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - return 1; - } - - std::string svc_role; - ret = get_catalog_service_role(svc_role); - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - return ret.code(); - } - - if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == svc_role) { - if (std::strstr(rsComm.myEnv.rodsDebug, "CAT") != nullptr) { - chlDebug(rsComm.myEnv.rodsDebug); - } - } - - status = initAgent(RULE_ENGINE_TRY_CACHE, &rsComm); - - if (status < 0) { - log_agent::error("agentMain :: initAgent failed: {}", status); - sendVersion(net_obj, SYS_AGENT_INIT_ERR, 0, nullptr, 0); - cleanupAndExit(status); - } - - if (rsComm.clientUser.userName[0] != '\0') { - status = chkAllowedUser(rsComm.clientUser.userName, rsComm.clientUser.rodsZone); - - if (status < 0) { - sendVersion(net_obj, status, 0, nullptr, 0); - cleanupAndExit(status); - } - } - - // handle negotiations with the client regarding TLS if requested - // this scope block makes valgrind happy - { - std::string neg_results; - ret = irods::client_server_negotiation_for_server(net_obj, neg_results); - if (!ret.ok() || neg_results == irods::CS_NEG_FAILURE) { - // send a 'we failed to negotiate' message here?? - // or use the error stack rule engine thingie - log_agent::error(PASS(ret).result()); - sendVersion(net_obj, SERVER_NEGOTIATION_ERROR, 0, nullptr, 0); - cleanupAndExit(ret.code()); - } - else { - // copy negotiation results to comm for action by network objects - std::snprintf(rsComm.negotiation_results, sizeof(rsComm.negotiation_results), "%s", neg_results.c_str()); - } - } - - // send the server version and status as part of the protocol. Put - // rsComm.reconnPort as the status - ret = sendVersion(net_obj, status, rsComm.reconnPort, rsComm.reconnAddr, rsComm.cookie); - - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - sendVersion(net_obj, SYS_AGENT_INIT_ERR, 0, nullptr, 0); - cleanupAndExit(status); - } - - logAgentProc(&rsComm); - - // call initialization for network plugin as negotiated - irods::network_object_ptr new_net_obj; - ret = irods::network_factory(&rsComm, new_net_obj); - if (!ret.ok()) { - return ret.code(); - } - - ret = sockAgentStart(new_net_obj); - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - return ret.code(); - } - - const auto cleanup_and_free_rsComm_members = [&rsComm] { - cleanup(); - if (rsComm.thread_ctx) { - std::free(rsComm.thread_ctx); // NOLINT(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory) - } - if (rsComm.auth_scheme) { - std::free(rsComm.auth_scheme); // NOLINT(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory) - } - }; - - new_net_obj->to_server(&rsComm); - status = agentMain(&rsComm); - - // call initialization for network plugin as negotiated - ret = sockAgentStop( new_net_obj ); - if (!ret.ok()) { - log_agent::error(PASS(ret).result()); - cleanup_and_free_rsComm_members(); - return ret.code(); - } - - new_net_obj->to_server(&rsComm); - - cleanup_and_free_rsComm_members(); - - // clang-format off - (0 == status) - ? log_agent::debug("Agent [{}] exiting with status = {}", getpid(), status) - : log_agent::error("Agent [{}] exiting with status = {}", getpid(), status); - // clang-format on - -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - // This function must be called here due to the use of _exit() (just below). Address Sanitizer (ASan) - // relies on std::atexit handlers to report its findings. _exit() does not trigger any of the handlers - // registered by ASan, therefore, we manually run ASan just before the agent exits. - __lsan_do_leak_check(); -#endif - - // _exit() must be called here due to a design limitation involving forked processes and mutexes. - // - // It has been observed that if the agent factory is respawned by the main server process, global - // mutexes will be locked 99% of the time. These global mutexes can never be unlocked following the - // call to fork(). - // - // iRODS makes use of C++ libraries that make assertions around the handling of mutexes (e.g. boost::mutex). - // If these assertions are violated, SIGABRT is triggered. For that reason, we cannot allow agents to - // execute std::exit() or return up the call chain. Doing so would result in SIGABRT. For the most part, - // using _exit() is perfectly fine here because this is the final step in shutting down the agent process. - // - // The key word here is process. Following this call, the OS will reclaim all memory associated with - // the terminated agent process. - _exit((0 == status) ? 0 : 1); -} - -static void set_rule_engine_globals(RsComm* _comm) -{ - irods::set_server_property(irods::CLIENT_USER_NAME_KW, _comm->clientUser.userName); - irods::set_server_property(irods::CLIENT_USER_ZONE_KW, _comm->clientUser.rodsZone); - irods::set_server_property(irods::CLIENT_USER_PRIV_KW, _comm->clientUser.authInfo.authFlag); - irods::set_server_property(irods::PROXY_USER_NAME_KW, _comm->proxyUser.userName); - irods::set_server_property(irods::PROXY_USER_ZONE_KW, _comm->proxyUser.rodsZone); - irods::set_server_property(irods::PROXY_USER_PRIV_KW, _comm->clientUser.authInfo.authFlag); -} // set_rule_engine_globals - -int agentMain(RsComm* rsComm) -{ - if (!rsComm) { - return SYS_INTERNAL_NULL_INPUT_ERR; - } - - int status = 0; - - // compiler backwards compatibility hack - // see header file for more details - irods::dynamic_cast_hack(); - - while (status >= 0) { - // set default to the native auth scheme here. - if (!rsComm->auth_scheme) { - rsComm->auth_scheme = strdup("native"); - } - // The following is an artifact of the legacy authentication plugins. This operation is - // only useful for certain plugins which are not supported in 4.3.0, so it is being - // left out of compilation for now. Once we have determined that this is safe to do in - // general, this section can be removed. -#if 0 - // construct an auth object based on the scheme specified in the comm - irods::auth_object_ptr auth_obj; - if (const auto err = irods::auth_factory(rsComm->auth_scheme, &rsComm->rError, auth_obj); !err.ok()) { - irods::experimental::api::plugin_lifetime_manager::destroy(); - - irods::log(PASSMSG(fmt::format( - "Failed to factory an auth object for scheme: \"{}\".", - rsComm->auth_scheme), err)); - - return err.code(); - } - - irods::plugin_ptr ptr; - if (const auto err = auth_obj->resolve(irods::AUTH_INTERFACE, ptr); !err.ok()) { - irods::experimental::api::plugin_lifetime_manager::destroy(); - - irods::log(PASSMSG(fmt::format( - "Failed to resolve the auth plugin for scheme: \"{}\".", - rsComm->auth_scheme), err)); - - return err.code(); - } - - irods::auth_ptr auth_plugin = boost::dynamic_pointer_cast(ptr); - - // Call agent start - if (const auto err = auth_plugin->call(rsComm, irods::AUTH_AGENT_START, auth_obj, ""); !err.ok()) { - irods::experimental::api::plugin_lifetime_manager::destroy(); - - irods::log(PASSMSG(fmt::format( - "Failed during auth plugin agent start for scheme: \"{}\".", - rsComm->auth_scheme), err)); - - return err.code(); - } -#endif - - // add the user info to the server properties for - // reach by the operation wrapper for access by the - // dynamic policy enforcement points - try { - set_rule_engine_globals(rsComm); - } - catch (const irods::exception& e) { - log_agent::error("set_rule_engine_globals failed:\n{}", e.what()); - } - - if (rsComm->ssl_do_accept) { - status = sslAccept( rsComm ); - if (status < 0) { - log_agent::error("sslAccept failed in agentMain with status {}", status); - } - rsComm->ssl_do_accept = 0; - } - if (rsComm->ssl_do_shutdown) { - status = sslShutdown(rsComm); - if (status < 0) { - log_agent::error("sslShutdown failed in agentMain with status {}", status); - } - rsComm->ssl_do_shutdown = 0; - } - - status = readAndProcClientMsg(rsComm, READ_HEADER_TIMEOUT); - if (status < 0) { - if (status == DISCONN_STATUS) { - status = 0; - break; - } - } - } - - irods::experimental::api::plugin_lifetime_manager::destroy(); - - // determine if we even need to connect, break the - // infinite reconnect loop. - if (!resc_mgr.need_maintenance_operations()) { - return status; - } - - // find the icat host - rodsServerHost_t* rodsServerHost = 0; - status = getRcatHost(PRIMARY_RCAT, 0, &rodsServerHost); - if (status < 0) { - log_agent::error(ERROR(status, "getRcatHost failed.").result()); - return status; - } - - // connect to the icat host - status = svrToSvrConnect( rsComm, rodsServerHost ); - if ( status < 0 ) { - log_agent::error(ERROR(status, "svrToSvrConnect failed.").result()); - return status; - } - - // call post disconnect maintenance operations before exit - status = resc_mgr.call_maintenance_operations(rodsServerHost->conn); - - return status; -} // agentMain diff --git a/server/core/src/rodsConnect.cpp b/server/core/src/rodsConnect.cpp index e2f6d75a6e..018923c323 100644 --- a/server/core/src/rodsConnect.cpp +++ b/server/core/src/rodsConnect.cpp @@ -298,9 +298,8 @@ int queueZone(const char* zoneName, rodsLog(LOG_DEBUG, "queueZone: primaryServerHost for %s is NULL", zoneName); return SYS_INVALID_SERVER_HOST; } - else { - return 0; - } + + return 0; } int diff --git a/server/core/src/rsApiHandler.cpp b/server/core/src/rsApiHandler.cpp index f10b8ed194..52910c1a68 100644 --- a/server/core/src/rsApiHandler.cpp +++ b/server/core/src/rsApiHandler.cpp @@ -1,4 +1,6 @@ #include "irods/rsApiHandler.hpp" + +#include "irods/agent_globals.hpp" #include "irods/modDataObjMeta.h" #include "irods/rcMisc.h" #include "irods/miscServerFunct.hpp" @@ -541,12 +543,23 @@ readAndProcClientMsg( rsComm_t * rsComm, int flags ) { while ( 1 ) { ret = readMsgHeader( net_obj, &myHeader, &tv ); if ( !ret.ok() ) { + log_agent::debug("{}: readMsgHeader() returned [{}]", __func__, ret.code()); + + if (ret.code() == INTERRUPT_DETECTED) { + // Check if the agent factory requested for the agent to stop. + if (g_terminate) { + log_agent::info("{}: Received instruction to shutdown. Agent is shutting down.", __func__, ret.code()); + return SHUTDOWN_SEQUENCE_INITIATED; + } + } + if ( isL1descInuse() && retryCnt < MAX_READ_HEADER_RETRY ) { rodsLogError( LOG_ERROR, status, "readAndProcClientMsg:readMsgHeader error. status = %d", ret.code() ); retryCnt++; continue; } + if ( ret.code() == USER_SOCK_CONNECT_TIMEDOUT ) { rodsLog( LOG_ERROR, "readAndProcClientMsg: readMsgHeader by pid %d timedout", @@ -559,6 +572,13 @@ readAndProcClientMsg( rsComm_t * rsComm, int flags ) { } else { ret = readMsgHeader( net_obj, &myHeader, NULL ); + if (!ret.ok() && ret.code() == INTERRUPT_DETECTED) { + // Check if the agent factory requested for the agent to stop. + if (g_terminate) { + log_agent::info("{}: Received instruction to shutdown. Agent is shutting down.", __func__, ret.code()); + return SHUTDOWN_SEQUENCE_INITIATED; + } + } } if ( !ret.ok() ) { diff --git a/server/delay_server/CMakeLists.txt b/server/delay_server/CMakeLists.txt index a782f61889..f229095a23 100644 --- a/server/delay_server/CMakeLists.txt +++ b/server/delay_server/CMakeLists.txt @@ -9,6 +9,7 @@ target_link_libraries( irods_server irods_client irods_common + "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_program_options.so" "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_system.so" "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_filesystem.so" "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_thread.so" diff --git a/server/delay_server/src/irodsDelayServer.cpp b/server/delay_server/src/irodsDelayServer.cpp index 0534afd727..5f854889bc 100644 --- a/server/delay_server/src/irodsDelayServer.cpp +++ b/server/delay_server/src/irodsDelayServer.cpp @@ -1,7 +1,9 @@ #include "irods/irodsDelayServer.hpp" +#include "boost/asio/ip/host_name.hpp" #include "irods/client_connection.hpp" #include "irods/connection_pool.hpp" +#include "irods/delay_rule_tag.h" #include "irods/fully_qualified_username.hpp" #include "irods/get_delay_rule_info.h" #include "irods/get_grid_configuration_value.h" @@ -9,6 +11,7 @@ #include "irods/irods_at_scope_exit.hpp" #include "irods/irods_client_api_table.hpp" #include "irods/irods_configuration_keywords.hpp" +#include "irods/irods_default_paths.hpp" #include "irods/irods_delay_queue.hpp" #include "irods/irods_get_full_path_for_config_file.hpp" #include "irods/irods_logger.hpp" @@ -17,6 +20,7 @@ #include "irods/irods_re_structs.hpp" #include "irods/irods_server_properties.hpp" #include "irods/irods_server_state.hpp" +#include "irods/irods_signal.hpp" #include "irods/json_deserialization.hpp" #include "irods/json_serialization.hpp" #include "irods/key_value_proxy.hpp" @@ -29,6 +33,7 @@ #include "irods/rodsClient.h" #include "irods/rodsDef.h" #include "irods/rodsErrorTable.h" +#include "irods/rodsKeyWdDef.h" #include "irods/rodsPackTable.h" #include "irods/rodsUser.h" #include "irods/rsGlobalExtern.hpp" @@ -36,14 +41,18 @@ #include "irods/ruleExecSubmit.h" #include "irods/server_utilities.hpp" #include "irods/thread_pool.hpp" +#include "irods/genquery2.h" #include +#include #include #include +#include + +#include #include #include -#include #include #include #include @@ -75,31 +84,26 @@ extern "C" const char* __asan_default_options() // clang-format off namespace ix = irods::experimental; -namespace logger = irods::experimental::log; - +using log_ds = irods::experimental::log::delay_server; using json = nlohmann::json; // clang-format on namespace { - std::atomic_bool delay_server_terminated{}; + volatile std::sig_atomic_t g_terminate = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - void init_logger( - const bool write_to_stdout, - const bool enable_test_mode) + void init_logger(pid_t _pid, const bool write_to_stdout, const bool enable_test_mode) { - logger::init(write_to_stdout, enable_test_mode); + namespace logger = irods::experimental::log; + + logger::init(_pid, write_to_stdout, enable_test_mode); logger::server::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_SERVER)); logger::legacy::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_LEGACY)); logger::delay_server::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_DELAY_SERVER)); logger::set_server_type("delay_server"); - - if (char hostname[HOST_NAME_MAX + 1]{}; gethostname(hostname, sizeof(hostname)) == 0) { - // NOLINTNEXTLINE(cppcoreguidelines-pro-bounds-array-to-pointer-decay) - logger::set_server_hostname(hostname); - } + logger::set_server_hostname(boost::asio::ip::host_name()); // Attach the zone name to the logger. // We can't use the server properties interface because it depends on the logger. @@ -127,6 +131,30 @@ namespace } } // init_logger + int setup_signal_handlers() + { + // DO NOT memset sigaction structures! + + // SIGINT + struct sigaction sa_terminate; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_terminate.sa_mask); + sa_terminate.sa_flags = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + sa_terminate.sa_handler = [](int) { g_terminate = 1; }; + if (sigaction(SIGINT, &sa_terminate, nullptr) == -1) { + return -1; + } + + // SIGTERM + if (sigaction(SIGTERM, &sa_terminate, nullptr) == -1) { + return -1; + } + + irods::setup_unrecoverable_signal_handlers(); + + return 0; + } // setup_signal_handlers + std::optional next_executor() { const auto config_handle{irods::server_properties::instance().map()}; @@ -166,7 +194,7 @@ namespace rodsEnv env{}; _getRodsEnv(env); - logger::delay_server::debug("Connecting to host [{}] as proxy user [{}] on behalf of user [{}] ...", + log_ds::debug("Connecting to host [{}] as proxy user [{}] on behalf of user [{}] ...", *executor, env.rodsUserName, *_client_user); irods::experimental::fully_qualified_username local_admin{env.rodsUserName, env.rodsZone}; @@ -177,10 +205,10 @@ namespace } } catch (...) { - logger::delay_server::error("Could not get the next delay rule executor."); + log_ds::error("Could not get the next delay rule executor."); } - logger::delay_server::debug("Connecting to local server using server credentials."); + log_ds::debug("Connecting to local server using server credentials."); return {}; } // get_new_connection @@ -280,7 +308,7 @@ namespace ruleExecSubmitInp_t& _inp, int _exec_status) { - logger::delay_server::trace("Updating rule's execution frequency [rule_id={}].", _inp.ruleExecId); + log_ds::trace("Updating rule's execution frequency [rule_id={}].", _inp.ruleExecId); // Prepare input for rule exec mod _exec_status = _exec_status > 0 ? 0 : _exec_status; @@ -296,8 +324,8 @@ namespace rstrcpy(rule_exec_del_inp.ruleExecId, _inp.ruleExecId, NAME_LEN); const int status = rcRuleExecDel(&_comm, &rule_exec_del_inp); if (status < 0) { - logger::delay_server::error("{}:{} - rcRuleExecDel failed {} for ID {}", - __FUNCTION__, __LINE__, status, rule_exec_del_inp.ruleExecId); + log_ds::error("{}:{} - rcRuleExecDel failed {} for ID {}", + __func__, __LINE__, status, rule_exec_del_inp.ruleExecId); } return status; }}; @@ -325,6 +353,9 @@ namespace addKeyVal(&rule_exec_mod_inp.condInput, RULE_PRIORITY_KW, "5"); } + // Clear the tag so the rule can be processed in the future. + addKeyVal(&rule_exec_mod_inp.condInput, RULE_EXE_STATUS_KW, ""); + addKeyVal(&rule_exec_mod_inp.condInput, RULE_LAST_EXE_TIME_KW, current_time); addKeyVal(&rule_exec_mod_inp.condInput, RULE_EXE_TIME_KW, next_time); if(repeat_rule) { @@ -333,8 +364,8 @@ namespace const int status = rcRuleExecMod(&_comm, &rule_exec_mod_inp); if (status < 0) { - logger::delay_server::error("{}:{} - rcRuleExecMod failed {} for rule ID {}", - __FUNCTION__, __LINE__, status, rule_exec_mod_inp.ruleId); + log_ds::error("{}:{} - rcRuleExecMod failed {} for rule ID {}", + __func__, __LINE__, status, rule_exec_mod_inp.ruleId); } if (rule_exec_mod_inp.condInput.len > 0) { @@ -344,8 +375,8 @@ namespace return status; }; - logger::delay_server::debug("[{}:{}] - time:[{}],ef:[{}],next:[{}]", - __FUNCTION__, __LINE__, current_time, ef_string, next_time); + log_ds::debug("[{}:{}] - time:[{}],ef:[{}],next:[{}]", + __func__, __LINE__, current_time, ef_string, next_time); const int repeat_status = getNextRepeatTime(current_time, ef_string, next_time); switch(repeat_status) { case 0: @@ -364,8 +395,8 @@ namespace // Delete if successful, otherwise update with new exec time and frequency return !_exec_status ? delete_rule_exec_info() : update_rule_exec_info(true); default: - logger::delay_server::error("{}:{} - getNextRepeatTime returned unknown value {} for rule ID {}", - __FUNCTION__, __LINE__, repeat_status, _inp.ruleExecId); + log_ds::error("{}:{} - getNextRepeatTime returned unknown value {} for rule ID {}", + __func__, __LINE__, repeat_status, _inp.ruleExecId); return repeat_status; } } // update_entry_for_repeat @@ -375,7 +406,7 @@ namespace const std::string_view _rei_file_path, const std::string_view _rule_exec_ctx) noexcept { - logger::delay_server::trace("Migrating REI file into catalog [rule_id={}] ...", _rule_id); + log_ds::trace("Migrating REI file into catalog [rule_id={}] ...", _rule_id); try { ruleExecModInp_t input{}; @@ -386,7 +417,7 @@ namespace kvp[RULE_EXECUTION_CONTEXT_KW] = _rule_exec_ctx.data(); if (const auto ec = rcRuleExecMod(&_comm, &input); ec < 0) { - logger::delay_server::error("Failed to migrate REI file into catalog [rule_id={}, error_code={}]", _rule_id, ec); + log_ds::error("Failed to migrate REI file into catalog [rule_id={}, error_code={}]", _rule_id, ec); return; } @@ -397,7 +428,7 @@ namespace boost::filesystem::rename(_rei_file_path.data(), new_file_name, ec); } catch (...) { - logger::delay_server::error("An unexpected error was encountered during REI file migration [rule_id={}].", _rule_id); + log_ds::error("An unexpected error was encountered during REI file migration [rule_id={}].", _rule_id); } } // migrate_rule_execution_context_into_catalog @@ -420,7 +451,7 @@ namespace int run_rule_exec(rcComm_t& _comm, ruleExecSubmitInp_t& _inp) { - logger::delay_server::trace("Generating rule execution context [rule_id={}].", _inp.ruleExecId); + log_ds::trace("Generating rule execution context [rule_id={}].", _inp.ruleExecId); ruleExecInfoAndArg_t* rei_and_arg{}; ruleExecInfo_t rei{}; @@ -443,7 +474,7 @@ namespace // The nullptr and zero (0) represent the argument vector and its size (i.e. argv and argc). // Pack the REI so that it is available on the server-side. if (const auto ec = packReiAndArg(&rei, nullptr, 0, &_inp.packedReiAndArgBBuf); ec < 0) { - logger::delay_server::error("Failed to pack rule execution information."); + log_ds::error("Failed to pack rule execution information."); return SYS_INTERNAL_ERR; } } @@ -463,7 +494,7 @@ namespace nullptr); if (ec < 0) { - logger::delay_server::error("Could not unpack struct [error_code={}].", ec); + log_ds::error("Could not unpack struct [error_code={}].", ec); return ec; } @@ -501,10 +532,10 @@ namespace }}; // Execute rule. - logger::delay_server::trace("Executing rule [rule_id={}].", _inp.ruleExecId); + log_ds::trace("Executing rule [rule_id={}].", _inp.ruleExecId); auto status = rcExecRuleExpression(&_comm, &exec_rule); - if (delay_server_terminated) { - logger::delay_server::info("Rule [{}] completed with status [{}] but delay server was terminated.", + if (g_terminate) { + log_ds::info("Rule [{}] completed with status [{}] but delay server was terminated.", _inp.ruleExecId, status); } @@ -516,44 +547,44 @@ namespace } if (strlen(_inp.exeFrequency) > 0) { - logger::delay_server::trace("Updating rule execution information for next run [rule_id={}].", _inp.ruleExecId); + log_ds::trace("Updating rule execution information for next run [rule_id={}].", _inp.ruleExecId); return update_entry_for_repeat(_comm, _inp, status); } if (status < 0) { - logger::delay_server::error("ruleExec of {}: {} failed.", _inp.ruleExecId, _inp.ruleName); + log_ds::error("ruleExec of {}: {} failed.", _inp.ruleExecId, _inp.ruleName); ruleExecDelInp_t rule_exec_del_inp{}; rstrcpy(rule_exec_del_inp.ruleExecId, _inp.ruleExecId, NAME_LEN); status = rcRuleExecDel(&_comm, &rule_exec_del_inp); if (status < 0) { - logger::delay_server::error("rcRuleExecDel failed for {}, stat={}", _inp.ruleExecId, status); + log_ds::error("rcRuleExecDel failed for {}, stat={}", _inp.ruleExecId, status); // Establish a new connection as the original may be invalid ix::client_connection conn; status = rcRuleExecDel(static_cast(conn), &rule_exec_del_inp); if (status < 0) { - logger::delay_server::error("rcRuleExecDel failed again for {}, stat={} - exiting", _inp.ruleExecId, status); + log_ds::error("rcRuleExecDel failed again for {}, stat={} - exiting", _inp.ruleExecId, status); } } return status; } // Success - remove rule from catalog - logger::delay_server::trace("Removing rule from catalog [rule_id={}].", _inp.ruleExecId); + log_ds::trace("Removing rule from catalog [rule_id={}].", _inp.ruleExecId); ruleExecDelInp_t rule_exec_del_inp{}; rstrcpy(rule_exec_del_inp.ruleExecId, _inp.ruleExecId, NAME_LEN); status = rcRuleExecDel(&_comm, &rule_exec_del_inp); if (status < 0) { - logger::delay_server::error("Failed deleting rule exec {} from catalog", rule_exec_del_inp.ruleExecId); + log_ds::error("Failed deleting rule exec {} from catalog", rule_exec_del_inp.ruleExecId); } - logger::delay_server::trace("Rule processed [rule_id={}].", _inp.ruleExecId); + log_ds::trace("Rule processed [rule_id={}].", _inp.ruleExecId); return status; } // run_rule_exec void execute_rule(irods::delay_queue& queue, const std::string_view rule_id) { - if (delay_server_terminated) { + if (g_terminate) { return; } @@ -566,10 +597,25 @@ namespace ix::client_connection conn = get_new_connection(std::nullopt); try { + // TODO Attach the hostname to the delay rule in the catalog only if the exe_status column is null. + // This is used to keep other delay servers from picking up delay rules being executed. If the + // attach operation fails (i.e. errors out or another delay server grabs it first), remove the + // delay rule from the in-memory queue and return. + DelayRuleTagInput input{}; + rule_id.copy(input.rule_id, sizeof(DelayRuleTagInput::rule_id)); + boost::asio::ip::host_name().copy(input.tag, sizeof(DelayRuleTagInput::tag)); + + // TODO Replace use of "tag" with "lock". + if (const auto ec = rc_delay_rule_tag(static_cast(conn), &input); ec < 0) { + log_ds::trace("{}: Rule ID [{}] has already been tagged. Removing from queue.", __func__, rule_id); + queue.dequeue_rule(std::string(rule_exec_submit_inp.ruleExecId)); + return; + } + rule_exec_submit_inp = fill_rule_exec_submit_inp(conn, rule_id); } catch (const irods::exception& e) { - if (delay_server_terminated) { + if (g_terminate) { // Get out! return; } @@ -579,18 +625,18 @@ namespace // in the catalog. If this is the case, we assume that an executor has completed // the rule since retrieving it in the main thread. Therefore, there is nothing // to do and the rule can be safely removed from the queue. - logger::delay_server::info("dequeueing rule because rule ID [{}] no longer exists in the catalog", rule_id); + log_ds::info("dequeueing rule because rule ID [{}] no longer exists in the catalog", rule_id); queue.dequeue_rule(std::string(rule_exec_submit_inp.ruleExecId)); } else { // Something serious happened - log it here. - logger::delay_server::error("[{}:{}] - [{}]", __func__, __LINE__, e.client_display_what()); + log_ds::error("[{}:{}] - [{}]", __func__, __LINE__, e.client_display_what()); } return; } catch (const std::exception& e) { - logger::delay_server::error("[{}:{}] - [{}]", __func__, __LINE__, e.what()); + log_ds::error("[{}:{}] - [{}]", __func__, __LINE__, e.what()); return; } @@ -598,20 +644,20 @@ namespace conn = get_new_connection(rule_exec_submit_inp.userName); try { if (const int status = run_rule_exec(conn, rule_exec_submit_inp); status < 0) { - logger::delay_server::error("Rule exec for [{}] failed. status = [{}]", rule_exec_submit_inp.ruleExecId, status); + log_ds::error("Rule exec for [{}] failed. status = [{}]", rule_exec_submit_inp.ruleExecId, status); } } catch (const std::exception& e) { - logger::delay_server::error("Exception caught during execution of rule [{}]: [{}]", + log_ds::error("Exception caught during execution of rule [{}]: [{}]", rule_exec_submit_inp.ruleExecId, e.what()); } - logger::delay_server::debug("rule [{}] complete", rule_exec_submit_inp.ruleExecId); - if (!delay_server_terminated) { - logger::delay_server::debug("dequeueing rule [{}]", rule_exec_submit_inp.ruleExecId); + log_ds::debug("rule [{}] complete", rule_exec_submit_inp.ruleExecId); + if (!g_terminate) { + log_ds::debug("dequeueing rule [{}]", rule_exec_submit_inp.ruleExecId); queue.dequeue_rule(std::string(rule_exec_submit_inp.ruleExecId)); } - logger::delay_server::debug("rule [{}] exists in queue: [{}]", rule_exec_submit_inp.ruleExecId, queue.contains_rule_id(rule_exec_submit_inp.ruleExecId)); + log_ds::debug("rule [{}] exists in queue: [{}]", rule_exec_submit_inp.ruleExecId, queue.contains_rule_id(rule_exec_submit_inp.ruleExecId)); } // execute_rule auto make_delay_queue_query_processor( @@ -627,13 +673,20 @@ namespace return; } - logger::delay_server::debug("Enqueueing rule ID [{}]", rule_id); + // TODO This check may be incorrect. Need to think about it more. + // Skip any rules which have been tagged. + if (!result[1].empty()) { + log_ds::trace("Delay rule was tagged with [{}] earlier. Ignoring rule ID [{}] for now.", result[1], rule_id); + return; + } + + log_ds::debug("Enqueueing rule ID [{}]", rule_id); try { queue.enqueue_rule(rule_id); } catch (const std::bad_alloc& e) { - logger::delay_server::trace("Delay queue memory limit reached. Ignoring rule ID [{}] for now.", rule_id); + log_ds::trace("Delay queue memory limit reached. Ignoring rule ID [{}] for now.", rule_id); return; } @@ -648,9 +701,9 @@ namespace // the rule to be rescheduled for execution. irods::at_scope_exit remove_rule_from_queue{[&] { try { - logger::delay_server::trace("Dequeuing rule ID [{}] ...", result[0]); + log_ds::trace("Dequeuing rule ID [{}] ...", result[0]); queue.dequeue_rule(result[0]); - logger::delay_server::trace("Rule ID [{}] dequeued successfully.", result[0]); + log_ds::trace("Rule ID [{}] dequeued successfully.", result[0]); } catch (...) {} }}; @@ -659,18 +712,24 @@ namespace execute_rule(queue, result[0]); } catch (const irods::exception& e) { - logger::delay_server::error(e.what()); + log_ds::error(e.what()); } catch (const std::exception& e) { - logger::delay_server::error(e.what()); + log_ds::error(e.what()); } catch (...) { - logger::delay_server::error("Caught an unknown error."); + log_ds::error("Caught an unknown error."); } }); }; - const auto qstr = fmt::format("SELECT RULE_EXEC_ID, ORDER_DESC(RULE_EXEC_PRIORITY) " + // TODO + // Update or replace query_processor with a version that uses GenQuery2. + // We need to be able to search for NULL entries. + // + // Alternatively, whenever a delay rule is inserted, we put an empty string + // in the RULE_EXE_STATUS column. + const auto qstr = fmt::format("SELECT RULE_EXEC_ID, RULE_EXEC_STATUS, ORDER_DESC(RULE_EXEC_PRIORITY) " "WHERE RULE_EXEC_TIME <= '{}'", std::time(nullptr)); return {qstr, job}; @@ -683,7 +742,7 @@ namespace char hn[HOST_NAME_MAX + 1]{}; if (const auto err = gethostname(hn, sizeof(hn)); err != 0) { - logger::delay_server::error("{}: Failed to retrieve local server's hostname. Error {}", __func__, err); + log_ds::error("{}: Failed to retrieve local server's hostname. Error {}", __func__, err); return false; } @@ -703,7 +762,7 @@ namespace irods::at_scope_exit free_output{[&output] { std::free(output); }}; if (const auto ec = rc_get_grid_configuration_value(static_cast(conn), &input, &output); ec != 0) { - logger::delay_server::warn("{}: Failed to retrieve leader config option. Error {}", __func__, ec); + log_ds::warn("{}: Failed to retrieve leader config option. Error {}", __func__, ec); return false; } @@ -718,59 +777,57 @@ namespace } // anonymous namespace -int main(int argc, char** argv) +int main(int _argc, char** _argv) { bool enable_test_mode = false; bool write_to_stdout = false; - int c{}; - while (EOF != (c = getopt(argc, argv, "tu"))) { - switch (c) { - case 't': - enable_test_mode = true; - break; - case 'u': - write_to_stdout = true; - break; - default: - std::cerr << "Only -t and -u are supported" << std::endl; - exit(1); - } - } - irods::server_properties::instance().capture(); + namespace po = boost::program_options; - init_logger(write_to_stdout, enable_test_mode); + po::options_description opts_desc{""}; - logger::delay_server::info("Initializing delay server ..."); + // clang-format off + opts_desc.add_options() + ("stdout", po::bool_switch(&write_to_stdout), "") + ("test-mode,t", po::bool_switch(&enable_test_mode), ""); + // clang-format on - const auto pid_file_fd = irods::create_pid_file(irods::PID_FILENAME_DELAY_SERVER); - if (pid_file_fd == -1) { + try { + po::variables_map vm; + po::store(po::command_line_parser(_argc, _argv).options(opts_desc).run(), vm); + po::notify(vm); + } + catch (const std::exception& e) { + fmt::print(stderr, "Error: {}\n", e.what()); return 1; } - set_ips_display_name(boost::filesystem::path{argv[0]}.filename().c_str()); + { + const auto config_file_path = irods::get_irods_config_directory() / "server_config.json"; + irods::server_properties::instance().init(config_file_path.c_str()); + } - load_client_api_plugins(); + init_logger(getppid(), write_to_stdout, enable_test_mode); + + log_ds::info("Initializing delay server ..."); - const auto signal_exit_handler = [](int) { delay_server_terminated.store(true); }; - signal(SIGINT, signal_exit_handler); - signal(SIGHUP, signal_exit_handler); - signal(SIGTERM, signal_exit_handler); - signal(SIGUSR1, signal_exit_handler); + set_ips_display_name(boost::filesystem::path{_argv[0]}.filename().c_str()); - irods::api_entry_table& api_tbl = irods::get_client_api_table(); - irods::pack_entry_table& pk_tbl = irods::get_pack_table(); - init_api_table(api_tbl, pk_tbl); + load_client_api_plugins(); + + if (setup_signal_handlers() == -1) { + log_ds::error("{}: Error setting up signal handlers for delay server.", __func__); + } const auto sleep_time = [] { try { return irods::get_advanced_setting(irods::KW_CFG_DELAY_SERVER_SLEEP_TIME_IN_SECONDS); } catch (...) { - logger::delay_server::warn("Could not retrieve [{}] from advanced settings configuration. " - "Using default value of {}.", - irods::KW_CFG_DELAY_SERVER_SLEEP_TIME_IN_SECONDS, - irods::default_delay_server_sleep_time_in_seconds); + log_ds::warn("Could not retrieve [{}] from advanced settings configuration. " + "Using default value of {}.", + irods::KW_CFG_DELAY_SERVER_SLEEP_TIME_IN_SECONDS, + irods::default_delay_server_sleep_time_in_seconds); } return irods::default_delay_server_sleep_time_in_seconds; @@ -783,13 +840,13 @@ int main(int argc, char** argv) // Loop until the server is signaled to shutdown or the max amount of time // to sleep has been reached. while (true) { - if (delay_server_terminated.load()) { - logger::delay_server::info("Delay server received shutdown signal."); + if (g_terminate) { + log_ds::info("Delay server received shutdown signal."); return; } if (std::chrono::system_clock::now() - start_time >= allowed_sleep_time) { - logger::delay_server::debug("Delay server is awake."); + log_ds::debug("Delay server is awake."); return; } @@ -802,10 +859,10 @@ int main(int argc, char** argv) return irods::get_advanced_setting(irods::KW_CFG_NUMBER_OF_CONCURRENT_DELAY_RULE_EXECUTORS); } catch (...) { - logger::delay_server::warn("Could not retrieve [{}] from advanced settings configuration. " - "Using default value of {}.", - irods::KW_CFG_NUMBER_OF_CONCURRENT_DELAY_RULE_EXECUTORS, - irods::default_number_of_concurrent_delay_executors); + log_ds::warn("Could not retrieve [{}] from advanced settings configuration. " + "Using default value of {}.", + irods::KW_CFG_NUMBER_OF_CONCURRENT_DELAY_RULE_EXECUTORS, + irods::default_number_of_concurrent_delay_executors); } return irods::default_number_of_concurrent_delay_executors; @@ -822,9 +879,9 @@ int main(int argc, char** argv) } } catch (...) { - logger::delay_server::warn("Could not retrieve [{}] from advanced settings configuration. " - "Delay server will use as much memory as necessary.", - irods::KW_CFG_MAX_SIZE_OF_DELAY_QUEUE_IN_BYTES); + log_ds::warn("Could not retrieve [{}] from advanced settings configuration. " + "Delay server will use as much memory as necessary.", + irods::KW_CFG_MAX_SIZE_OF_DELAY_QUEUE_IN_BYTES); } return 0; @@ -833,45 +890,64 @@ int main(int argc, char** argv) irods::delay_queue queue{queue_size_in_bytes}; try { - while (!delay_server_terminated) { + while (!g_terminate) { try { - irods::server_properties::instance().capture(); if (!is_local_server_defined_as_delay_server_leader()) { - logger::delay_server::warn("This server is not the leader. Terminating..."); + log_ds::warn("This server is not the leader. Terminating..."); break; } + +// TODO +#if 1 auto delay_queue_processor = make_delay_queue_query_processor(thread_pool, queue); - logger::delay_server::trace("Gathering rules for execution ..."); + log_ds::trace("Gathering rules for execution ..."); ix::client_connection query_conn; auto future = delay_queue_processor.execute(thread_pool, static_cast(query_conn)); - logger::delay_server::trace("Waiting for rules to finish processing ..."); + log_ds::trace("Waiting for rules to finish processing ..."); auto errors = future.get(); - logger::delay_server::trace("Rules have been processed. Checking for errors ..."); + log_ds::trace("Rules have been processed. Checking for errors ..."); if (errors.size() > 0) { for (const auto& [code, msg] : errors) { - logger::delay_server::error("Executing delayed rule failed - [{}]::[{}]", code, msg); + log_ds::error("Executing delayed rule failed - [{}]::[{}]", code, msg); } } +#else + irods::experimental::client_connection conn; + + Genquery2Input input{}; + + auto query_str = fmt::format("select DELAY_RULE_ID, DELAY_RULE_PRIORITY where DELAY_RULE_EXE_TIME <= '{}' and (DELAY_RULE_STATUS is null or DELAY_RULE_STATUS = '')", std::time(nullptr)); + input.query_string = query_str.data(); + + char* output{}; + irods::at_scope_exit free_output{[&output] { std::free(output); }}; + + if (const auto ec = rc_genquery2(static_cast(conn), &input, &output); ec < 0) { + } + + const auto rows = json::parse(output); + +#endif } catch (const irods::exception& e) { - logger::delay_server::error(e.what()); + log_ds::error(e.what()); } catch (const std::exception& e) { - logger::delay_server::error(e.what()); + log_ds::error(e.what()); } - logger::delay_server::trace("Delay server is going to sleep."); + log_ds::trace("Delay server is going to sleep."); go_to_sleep(); } } catch (const irods::exception& e) { - logger::delay_server::error(e.what()); + log_ds::error(e.what()); } - logger::delay_server::info("Delay server exited normally."); + log_ds::info("Delay server exited normally."); #if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) __lsan_do_leak_check(); diff --git a/server/icat/include/irods/icatHighLevelRoutines.hpp b/server/icat/include/irods/icatHighLevelRoutines.hpp index 2d3d66f905..106ad6e630 100644 --- a/server/icat/include/irods/icatHighLevelRoutines.hpp +++ b/server/icat/include/irods/icatHighLevelRoutines.hpp @@ -411,4 +411,33 @@ auto chl_check_auth_credentials(RsComm& _comm, auto chl_execute_genquery2_sql(RsComm& _comm, const char* _sql, const std::vector* _values, char** _output) -> int; +/// \brief High-level wrapper for tagging delay rules. +/// +/// Triggers policy associated with database operations. +/// +/// \param[in] _comm The communication object. +/// \param[in] _rule_id +/// \param[out] _tag +/// +/// \return An integer. +/// \retval 0 On success. +/// \retval <0 On failure. +/// +/// \since 5.0.0 +auto chl_delay_rule_tag(RsComm& _comm, const char* _rule_id, const char* _tag) -> int; + +/// \brief High-level wrapper for removing tags from delay rules. +/// +/// Triggers policy associated with database operations. +/// +/// \param[in] _comm The communication object. +/// \param[in] _rule_id +/// +/// \return An integer. +/// \retval 0 On success. +/// \retval <0 On failure. +/// +/// \since 5.0.0 +auto chl_delay_rule_tag_clear(RsComm& _comm, const char* _rule_id) -> int; + #endif // IRODS_ICAT_HIGHLEVEL_ROUTINES_HPP diff --git a/server/icat/src/icatHighLevelRoutines.cpp b/server/icat/src/icatHighLevelRoutines.cpp index 29b3e6423a..4ef91cca3a 100644 --- a/server/icat/src/icatHighLevelRoutines.cpp +++ b/server/icat/src/icatHighLevelRoutines.cpp @@ -4895,3 +4895,53 @@ auto chl_execute_genquery2_sql(RsComm& _comm, const char* _sql, const std::vecto // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) return ret.code(); } // chl_execute_genquery2_sql + +auto chl_delay_rule_tag(RsComm& _comm, const char* _rule_id, const char* _tag) -> int +{ + irods::database_object_ptr db_obj_ptr; + if (const auto ret = irods::database_factory(database_plugin_type, db_obj_ptr); !ret.ok()) { + irods::log(PASS(ret)); + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return ret.code(); + } + + irods::plugin_ptr db_plug_ptr; + if (const auto ret = db_obj_ptr->resolve(irods::DATABASE_INTERFACE, db_plug_ptr); !ret.ok()) { + irods::log(PASSMSG("failed to resolve database interface", ret)); + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return ret.code(); + } + + irods::first_class_object_ptr ptr = boost::dynamic_pointer_cast(db_obj_ptr); + irods::database_ptr db = boost::dynamic_pointer_cast(db_plug_ptr); + + const auto ret = db->call(&_comm, irods::DATABASE_OP_DELAY_RULE_TAG, ptr, _rule_id, _tag); + + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return ret.code(); +} // chl_delay_rule_tag + +auto chl_delay_rule_tag_clear(RsComm& _comm, const char* _rule_id) -> int +{ + irods::database_object_ptr db_obj_ptr; + if (const auto ret = irods::database_factory(database_plugin_type, db_obj_ptr); !ret.ok()) { + irods::log(PASS(ret)); + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return ret.code(); + } + + irods::plugin_ptr db_plug_ptr; + if (const auto ret = db_obj_ptr->resolve(irods::DATABASE_INTERFACE, db_plug_ptr); !ret.ok()) { + irods::log(PASSMSG("failed to resolve database interface", ret)); + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return ret.code(); + } + + irods::first_class_object_ptr ptr = boost::dynamic_pointer_cast(db_obj_ptr); + irods::database_ptr db = boost::dynamic_pointer_cast(db_plug_ptr); + + const auto ret = db->call(&_comm, irods::DATABASE_OP_DELAY_RULE_TAG_CLEAR, ptr, _rule_id); + + // NOLINTNEXTLINE(bugprone-narrowing-conversions,cppcoreguidelines-narrowing-conversions) + return ret.code(); +} // chl_delay_rule_tag_clear diff --git a/server/irods_grid/CMakeLists.txt b/server/irods_grid/CMakeLists.txt deleted file mode 100644 index 2a4157725d..0000000000 --- a/server/irods_grid/CMakeLists.txt +++ /dev/null @@ -1,46 +0,0 @@ -# irods-grid was previously part of the iCommands repo - -add_executable( - irods-grid - "${CMAKE_CURRENT_SOURCE_DIR}/src/irods-grid.cpp" -) -target_link_libraries( - irods-grid - PRIVATE - irods_common - irods_plugin_dependencies - irods_client - irods_server_control_plane - "${IRODS_EXTERNALS_FULLPATH_AVRO}/lib/libavrocpp.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_chrono.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_filesystem.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_regex.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_program_options.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_system.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_thread.so" - "${IRODS_EXTERNALS_FULLPATH_ZMQ}/lib/libzmq.so" - ${CMAKE_DL_LIBS} - m -) -target_include_directories( - irods-grid - PRIVATE - "${IRODS_EXTERNALS_FULLPATH_AVRO}/include" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/include" - "${IRODS_EXTERNALS_FULLPATH_CPPZMQ}/include" - "${IRODS_EXTERNALS_FULLPATH_ZMQ}/include" -) -target_compile_definitions( - irods-grid - PRIVATE - ${IRODS_COMPILE_DEFINITIONS_PRIVATE} -) - -add_dependencies(all-server irods-grid) -install( - TARGETS - irods-grid - RUNTIME - DESTINATION "${CMAKE_INSTALL_SBINDIR}" - COMPONENT ${IRODS_PACKAGE_COMPONENT_SERVER_NAME} -) diff --git a/server/irods_grid/src/irods-grid.cpp b/server/irods_grid/src/irods-grid.cpp deleted file mode 100644 index e8b53d7e82..0000000000 --- a/server/irods_grid/src/irods-grid.cpp +++ /dev/null @@ -1,475 +0,0 @@ -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include - -#include "irods/rodsClient.h" -#include "irods/irods_server_control_plane.hpp" -#include "irods/irods_buffer_encryption.hpp" -#include "irods/server_control_plane_command.hpp" -#include "irods/irods_buffer_encryption.hpp" -#include "irods/irods_exception.hpp" - -#include - -template -irods::error usage(T& ostream) { - ostream << "Usage: 'irods-grid action [ option ] target'" << std::endl; - ostream << " " << std::endl; - ostream << "Sends a message on control channel for possible grid-wide operations" << std::endl; - ostream << " " << std::endl; - ostream << "action: ( required ) status, ping, pause, resume, shutdown" << std::endl; - ostream << " " << std::endl; - ostream << "option: --force-after=seconds or --wait-forever" << std::endl; - ostream << " " << std::endl; - ostream << "target: ( required ) --all, --hosts=,,..." << std::endl; - ostream << " " << std::endl; - ostream << "Examples:" << std::endl; - ostream << " " << std::endl; - ostream << "Status - returns a status of the server (or servers) requested in a validated" << std::endl; - ostream << "json document which includes iRODS Server PID, iRODS Server hostname," << std::endl; - ostream << "Delay Server PID, Agent PIDs and their age, Server status" << std::endl; - ostream << " irods-grid status --all" << std::endl; - ostream << " " << std::endl; - ostream << "Ping - attempt a connection to a server or servers" << std::endl; - ostream << " irods-grid ping --all" << std::endl; - ostream << " " << std::endl; - ostream << "Pause - suspend all incoming connections while allowing existing client connections to complete" << std::endl; - ostream << " irods-grid pause --all" << std::endl; - ostream << " " << std::endl; - ostream << "Resume - allow incoming connections from new clients" << std::endl; - ostream << " irods-grid resume --all" << std::endl; - ostream << " " << std::endl; - ostream << "Shutdown - gracefully shutdown an iRODS server or servers after allowing existing client connections to complete" << std::endl; - ostream << " irods-grid shutdown --force-after=5 --all" << std::endl; - ostream << " " << std::endl; - ostream << " irods-grid shutdown --wait-forever --all" << std::endl; - - return ERROR( SYS_INVALID_INPUT_PARAM, "usage" ); - -} // usage - -irods::error parse_program_options( - int _argc, - char* _argv[], - irods::control_plane_command& _cmd ) { - - namespace po = boost::program_options; - // clang-format off - po::options_description opt_desc( "options" ); - opt_desc.add_options() - ( "action", "either 'status', 'ping', 'shutdown', 'pause', or 'resume'" ) - ( "help,h", "show command usage" ) - ( "all", "operation applies to all servers in the grid" ) - ( "hosts", po::value(), "operation applies to a list of hosts in the grid" ) - ( "force-after", po::value(), "force shutdown after N seconds" ) - ( "wait-forever", "wait indefinitely for a graceful shutdown" ) - ( "ping", "attempt a connection to a server(s)" ) - ( "status", "display status a server(s)" ) - ( "shutdown", "gracefully shutdown a server(s)" ) - ( "pause", "refuse new client connections" ) - ( "reload", "Reload a server(s) configuration") - ( "resume", "allow new client connections" ); - // clang-format on - - po::positional_options_description pos_desc; - pos_desc.add( "action", 1 ); - - po::variables_map vm; - try { - po::store( - po::command_line_parser( - _argc, _argv ).options( - opt_desc ).positional( - pos_desc ).run(), vm ); - po::notify( vm ); - } - catch ( po::error& _e ) { - std::cerr << std::endl - << "Error: " - << _e.what() - << std::endl - << std::endl; - return usage(std::cerr); - - } - - // capture the 'subcommand' or 'action' to perform on the grid - irods::control_plane_command cmd; - if ( vm.count( "action" ) ) { - try { - const std::string& action = vm["action"].as(); - std::unordered_map< std::string, std::string > cmd_map; - // clang-format off - cmd_map[ "status" ] = irods::SERVER_CONTROL_STATUS; - cmd_map[ "ping" ] = irods::SERVER_CONTROL_PING; - cmd_map[ "pause" ] = irods::SERVER_CONTROL_PAUSE; - cmd_map[ "resume" ] = irods::SERVER_CONTROL_RESUME; - cmd_map[ "shutdown" ] = irods::SERVER_CONTROL_SHUTDOWN; - cmd_map[ "reload" ] = irods::SERVER_CONTROL_RELOAD; - // clang-format on - - if ( cmd_map.end() == cmd_map.find( action ) ) { - std::cerr << "invalid subcommand [" - << action - << "]" - << std::endl; - return usage(std::cerr); - - } - - _cmd.command = cmd_map[ action ]; - } - catch ( const boost::bad_any_cast& ) { - return ERROR( INVALID_ANY_CAST, "Attempt to cast vm[\"action\"] to std::string failed." ); - } - } - else { - return usage(std::cout); - - } - - if ( vm.count( "force-after" ) ) { - try { - size_t secs = vm[ "force-after" ].as(); - std::stringstream ss; ss << secs; - _cmd.options[ irods::SERVER_CONTROL_FORCE_AFTER_KW ] = - ss.str(); - } - catch ( const boost::bad_any_cast& ) { - return ERROR( INVALID_ANY_CAST, "Attempt to cast vm[\"force-after\"] to std::string failed." ); - } - } - else if ( vm.count( "wait-forever" ) ) { - _cmd.options[ irods::SERVER_CONTROL_WAIT_FOREVER_KW ] = - "keep_waiting"; - - } - - // capture either the 'all' servers or the hosts list - if ( vm.count( "all" ) ) { - _cmd.options[ irods::SERVER_CONTROL_OPTION_KW ] = - irods::SERVER_CONTROL_ALL_OPT; - - } - else if ( vm.count( "hosts" ) ) { - _cmd.options[ irods::SERVER_CONTROL_OPTION_KW ] = - irods::SERVER_CONTROL_HOSTS_OPT; - - std::vector< std::string > hosts; - try { - boost::split( - hosts, - vm[ "hosts" ].as(), - boost::is_any_of( ", " ), - boost::token_compress_on ); - } - catch ( const boost::bad_function_call& ) { - return ERROR( BAD_FUNCTION_CALL, "Boost threw bad_function_call." ); - } - catch ( const boost::bad_any_cast& ) { - return ERROR( INVALID_ANY_CAST, "Attempt to cast vm[\"hosts\"] to std::string failed." ); - } - - for ( size_t i = 0; - i < hosts.size(); - ++i ) { - std::stringstream ss; ss << i; - _cmd.options[ irods::SERVER_CONTROL_HOST_KW + ss.str() ] = - hosts[ i ]; - - } // for i - - } - else { - return usage(std::cerr); - - } - - return SUCCESS(); - -} // parse_program_options - -irods::error prepare_command_for_transport( - const rodsEnv& _env, - const irods::control_plane_command& _cmd, - irods::buffer_crypt::array_t& _data_to_send ) { - - // build a encryption context - std::string encryption_key( _env.irodsCtrlPlaneKey ); - irods::buffer_crypt crypt( - encryption_key.size(), // key size - 0, // salt size ( we dont send a salt ) - _env.irodsCtrlPlaneEncryptionNumHashRounds, - _env.irodsCtrlPlaneEncryptionAlgorithm ); - - // serialize using the generated avro class - auto out = avro::memoryOutputStream(); - avro::EncoderPtr e = avro::binaryEncoder(); - e->init( *out ); - avro::encode( *e, _cmd ); - std::shared_ptr> data = avro::snapshot(*out); - - // encrypt outgoing request - std::vector< unsigned char > enc_data( - data->begin(), - data->end() ); - - irods::buffer_crypt::array_t iv; - irods::buffer_crypt::array_t in_buf( - enc_data.begin(), - enc_data.end() ); - irods::buffer_crypt::array_t shared_secret( - encryption_key.begin(), - encryption_key.end() ); - irods::error ret = crypt.encrypt( - shared_secret, - iv, - in_buf, - _data_to_send ); - if ( !ret.ok() ) { - return PASS( ret ); - - } - - return SUCCESS(); - -} // prepare_command_for_transport - -irods::error decrypt_response( - const rodsEnv& _env, - const uint8_t* _data_ptr, - const size_t _data_size, - std::string& _rep_str ) { - - irods::buffer_crypt::array_t in_buf; - in_buf.assign( - _data_ptr, - _data_ptr + _data_size ); - - std::string encryption_key( _env.irodsCtrlPlaneKey ); - irods::buffer_crypt crypt( - encryption_key.size(), // key size - 0, // salt size ( we dont send a salt ) - _env.irodsCtrlPlaneEncryptionNumHashRounds, - _env.irodsCtrlPlaneEncryptionAlgorithm ); - - - irods::buffer_crypt::array_t iv; - irods::buffer_crypt::array_t shared_secret( - encryption_key.begin(), - encryption_key.end() ); - irods::buffer_crypt::array_t decoded_data; - irods::error ret = crypt.decrypt( - shared_secret, - iv, - in_buf, - decoded_data ); - if ( !ret.ok() ) { - return PASS( ret ); - - } - - _rep_str.assign( - decoded_data.begin(), - decoded_data.begin() + decoded_data.size() ); - - return SUCCESS(); - -} // decrypt_response - -std::string format_grid_message( const std::string& _status) -{ - std::string status = "{\n \"hosts\": [\n"; - status += _status; - status += "] \n}"; - - std::string::size_type pos = status.find_last_of( "," ); - if ( std::string::npos != pos ) { - status.erase( pos, 1 ); - } - else { - // possible error message - THROW( -1, std::string("server responded with an error\n") + _status ); - } - - using json = nlohmann::json; - - json obj; - - try { - obj = json::parse(static_cast(status.data())); - } - catch (const json::parse_error& e) { - if (std::string::npos != _status.find(irods::SERVER_PAUSED_ERROR)) { - THROW(-1, std::string{"server paused error\n"} + _status); - } - - THROW(-1, boost::format("json::parse failed with error: %s\n") % e.what()); - } - - status = obj.dump(4); - - return status; - -} // format_grid_message - -irods::error get_and_verify_client_environment( - rodsEnv& _env ) { - _getRodsEnv( _env ); - - bool have_error = false; - std::string msg = "missing environment entries: "; - if( 0 == strlen( _env.irodsCtrlPlaneKey ) ) { - have_error = true; - msg += "irods_server_control_plane_key"; - } - - if( 0 == _env.irodsCtrlPlaneEncryptionNumHashRounds ) { - have_error = true; - msg += ", irods_server_control_plane_encryption_num_hash_rounds"; - } - - if( 0 == strlen( _env.irodsCtrlPlaneEncryptionAlgorithm ) ) { - have_error = true; - msg += ", irods_server_control_plane_encryption_algorithm"; - } - - if( 0 == _env.irodsCtrlPlanePort ) { - have_error = true; - msg += ", irods_server_control_plane_port"; - } - - if( have_error ) { - return ERROR( - USER_INVALID_CLIENT_ENVIRONMENT, - msg ); - } - - return SUCCESS(); - -} // verify_client_environment - -int main( - int _argc, - char* _argv[] ) { - - irods::control_plane_command cmd; - irods::error ret = parse_program_options( - _argc, - _argv, - cmd ); - if ( !ret.ok() ) { - return 0; - - } - - rodsEnv env; - irods::error err = get_and_verify_client_environment( env ); - if( !err.ok() ) { - std::cerr << err.result(); - return 1; - } - - irods::buffer_crypt::array_t data_to_send; - ret = prepare_command_for_transport( - env, - cmd, - data_to_send ); - if ( !ret.ok() ) { - std::cerr << ret.result() - << std::endl; - return ret.code(); - - } - - // this is the client so we connect rather than bind - std::stringstream port_sstr; - port_sstr << env.irodsCtrlPlanePort; - std::string bind_str( "tcp://localhost:" ); - bind_str += port_sstr.str(); - try { - // standard zmq rep-req communication pattern - zmq::context_t zmq_ctx( 1 ); - zmq::socket_t zmq_skt( zmq_ctx, ZMQ_REQ ); - try { - zmq_skt.connect(bind_str.c_str()); - } - catch ( const zmq::error_t& ) { - std::cerr << "ZeroMQ encountered an error connecting to a socket.\n"; - return 1; - } - - // copy binary encoding into a zmq message for transport - zmq::message_t req( data_to_send.size() ); - memcpy( - req.data(), - data_to_send.data(), - data_to_send.size() ); - try { - if (!zmq_skt.send(req, zmq::send_flags::none)) { - std::cerr << "ZeroMQ encountered an error sending a message.\n"; - return errno; - } - } - catch ( const zmq::error_t& ) { - std::cerr << "ZeroMQ encountered an error sending a message.\n"; - return 1; - } - - zmq::message_t rep; - // wait for the server response - try { - if (!zmq_skt.recv(rep)) { - std::cerr << "ZeroMQ encountered an error receiving a message.\n"; - return errno; - } - } - catch ( const zmq::error_t& ) { - std::cerr << "ZeroMQ encountered an error receiving a message.\n"; - return 1; - } - - // decrypt the response - std::string rep_str; - ret = decrypt_response( - env, - static_cast< const uint8_t* >( rep.data() ), - rep.size(), - rep_str ); - if ( !ret.ok() ) { - irods::error err = PASS( ret ); - std::cerr << err.result() - << std::endl; - return 1; - - } - - if ( irods::SERVER_CONTROL_SUCCESS != rep_str ) { - try { - rep_str = format_grid_message( rep_str ); - std::cout << rep_str << std::endl; - } catch ( const irods::exception& e_ ) { - std::cerr << e_.message_stack()[0]; - } - } - - } - catch ( const zmq::error_t& ) { - std::cerr << "ZeroMQ encountered an error.\n"; - return 1; - - } - - return 0; - -} // main diff --git a/server/main_server/CMakeLists.txt b/server/main_server/CMakeLists.txt index bf428d55b2..c91d8f7434 100644 --- a/server/main_server/CMakeLists.txt +++ b/server/main_server/CMakeLists.txt @@ -1,7 +1,6 @@ add_executable( irodsServer - "${CMAKE_CURRENT_SOURCE_DIR}/src/rodsServer.cpp" - "${CMAKE_CURRENT_SOURCE_DIR}/src/main_server_control_plane.cpp" + "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cpp" ) target_link_libraries( irodsServer @@ -10,14 +9,11 @@ target_link_libraries( irods_common irods_plugin_dependencies irods_server - irods_server_control_plane - "${IRODS_EXTERNALS_FULLPATH_AVRO}/lib/libavrocpp.so" "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_chrono.so" "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_filesystem.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_regex.so" + "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_program_options.so" "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_system.so" - "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_thread.so" - "${IRODS_EXTERNALS_FULLPATH_ZMQ}/lib/libzmq.so" + "${IRODS_EXTERNALS_FULLPATH_NANODBC}/lib/libnanodbc.so" rt ${CMAKE_DL_LIBS} m @@ -27,10 +23,9 @@ target_include_directories( PRIVATE "$" "$" - "${IRODS_EXTERNALS_FULLPATH_AVRO}/include" + "$" "${IRODS_EXTERNALS_FULLPATH_BOOST}/include" - "${IRODS_EXTERNALS_FULLPATH_CPPZMQ}/include" - "${IRODS_EXTERNALS_FULLPATH_ZMQ}/include" + "${IRODS_EXTERNALS_FULLPATH_NANODBC}/include" ) target_compile_definitions( irodsServer @@ -49,9 +44,44 @@ install( COMPONENT ${IRODS_PACKAGE_COMPONENT_SERVER_NAME} ) +add_executable( + irodsAgent + "${CMAKE_CURRENT_SOURCE_DIR}/src/agent_main.cpp" +) +target_link_libraries( + irodsAgent + PRIVATE + fmt::fmt + irods_common + irods_plugin_dependencies + irods_server + "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_filesystem.so" + "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_program_options.so" + "${IRODS_EXTERNALS_FULLPATH_BOOST}/lib/libboost_system.so" + rt + ${CMAKE_DL_LIBS} + m +) +target_include_directories( + irodsAgent + PRIVATE + "$" + "$" + "${IRODS_EXTERNALS_FULLPATH_BOOST}/include" +) +target_compile_definitions( + irodsAgent + PRIVATE + ${IRODS_COMPILE_DEFINITIONS_PRIVATE} + ENABLE_RE + IRODS_ENABLE_SYSLOG +) + +add_dependencies(all-server irodsAgent) install( - FILES - "${CMAKE_CURRENT_SOURCE_DIR}/include/irods/rodsServer.hpp" - DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/irods" - COMPONENT ${IRODS_PACKAGE_COMPONENT_DEVELOPMENT_NAME} + TARGETS + irodsAgent + RUNTIME + DESTINATION "${CMAKE_INSTALL_SBINDIR}" + COMPONENT ${IRODS_PACKAGE_COMPONENT_SERVER_NAME} ) diff --git a/server/main_server/include/irods/rodsServer.hpp b/server/main_server/include/irods/rodsServer.hpp deleted file mode 100644 index f75c56e55c..0000000000 --- a/server/main_server/include/irods/rodsServer.hpp +++ /dev/null @@ -1,100 +0,0 @@ -#ifndef IRODS_RODS_SERVER_HPP -#define IRODS_RODS_SERVER_HPP - -#include "irods/rods.h" -#include "irods/rcGlobalExtern.h" // client global -#include "irods/rodsLog.h" -#include "irods/sockComm.h" -#include "irods/rsIcatOpr.hpp" -#include "irods/getRodsEnv.h" -#include "irods/rcConnect.h" - -#include -#include - -#include -#include -#include - -extern char *optarg; -extern int optind; -extern int opterr; -extern int optopt; - -#define MAX_EXEC_ENV 10 // max number of env for execv. -#define MAX_SVR_SVR_CONNECT_CNT 7 // avoid recurive connect. - -#define MIN_AGENT_TIMEOUT_TIME 7200 - -#define MAX_ACCEPT_ERR_CNT 1000 - -#define NUM_READ_WORKER_THR 5 - -#define RE_CACHE_SALT_NUM_RANDOM_BYTES 40 - -#define AGENT_QUE_CHK_INT 600 // check the agent queue every 600 sec for consistence. - -// Managing the spawned agents -typedef struct agentProc -{ - int pid; - int sock; - startupPack_t startupPack; - struct sockaddr_in remoteAddr; // remote address - struct agentProc* next; -} agentProc_t; - -int serverMain(const bool enable_test_mode, const bool write_to_stdout); - -int procChildren(agentProc_t** agentProcHead); - -agentProc_t* getAgentProcByPid(int childPid, agentProc_t** agentProcHead); - -#if defined(linux_platform) || defined(aix_platform) || defined(solaris_platform) || defined(osx_platform) -void serverExit(int sig); -#else -void serverExit(); -#endif - -void usage(char* prog); - -int initServer(rsComm_t* svrComm); - -int spawnAgent(agentProc_t* connReq, agentProc_t** agentProcHead); - -int execAgent(int newSock, startupPack_t* startupPack); - -int getAgentProcCnt(); - -int chkAgentProcCnt(); - -int getAgentProcPIDs(std::vector& _pids); - -int chkConnectedAgentProcQueue(); - -int recordServerProcess(rsComm_t* svrComm); - -int initServerMain(rsComm_t* svrComm, const bool enable_test_mode, const bool write_to_stdout); - -int addConnReqToQueue(rsComm_t* rsComm, int sock); - -int initConnThreadEnv(); - -agentProc_t* getConnReqFromQueue(); - -void readWorkerTask(); - -int procSingleConnReq(agentProc_t* connReq); - -int startProcConnReqThreads(); - -void stopProcConnReqThreads(); - -void spawnManagerTask(); - -int procBadReq(); - -void task_spawn_manager(); - -#endif // IRODS_RODS_SERVER_HPP - diff --git a/server/main_server/src/agent_main.cpp b/server/main_server/src/agent_main.cpp new file mode 100644 index 0000000000..ee6bffea28 --- /dev/null +++ b/server/main_server/src/agent_main.cpp @@ -0,0 +1,1317 @@ +#include "irods/agent_globals.hpp" +#include "irods/client_api_allowlist.hpp" +#include "irods/dns_cache.hpp" +#include "irods/hostname_cache.hpp" +#include "irods/initServer.hpp" +#include "irods/irods_at_scope_exit.hpp" +#include "irods/irods_buffer_encryption.hpp" // For RE cache salt +#include "irods/irods_client_api_table.hpp" +#include "irods/irods_client_server_negotiation.hpp" +#include "irods/irods_configuration_keywords.hpp" +#include "irods/irods_configuration_parser.hpp" // For key_path_t +#include "irods/irods_default_paths.hpp" +#include "irods/irods_dynamic_cast.hpp" +#include "irods/irods_environment_properties.hpp" +#include "irods/irods_exception.hpp" +#include "irods/irods_logger.hpp" +#include "irods/irods_network_factory.hpp" +#include "irods/irods_network_object.hpp" +#include "irods/irods_re_plugin.hpp" +#include "irods/irods_server_api_table.hpp" +#include "irods/irods_server_properties.hpp" +#include "irods/irods_signal.hpp" +#include "irods/irods_threads.hpp" +#include "irods/locks.hpp" // For removeMutex TODO remove eventually +#include "irods/miscServerFunct.hpp" // For get_catalog_service_role +#include "irods/plugin_lifetime_manager.hpp" +#include "irods/rcConnect.h" +#include "irods/rcGlobalExtern.h" // For ProcessType +#include "irods/replica_access_table.hpp" +#include "irods/replica_state_table.hpp" +#include "irods/rodsConnect.h" +#include "irods/rodsErrorTable.h" +#include "irods/rsApiHandler.hpp" +#include "irods/rsGlobalExtern.hpp" +#include "irods/rsIcatOpr.hpp" +#include "irods/sharedmemory.hpp" +#include "irods/sockComm.h" +#include "irods/sockCommNetworkInterface.hpp" +#include "irods/sslSockComm.h" + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_filesystem +# include +#else +# include +#endif + +// __has_feature is a Clang specific feature. +// The preprocessor code below exists so that other compilers can be used (e.g. GCC). +#ifndef __has_feature +# define __has_feature(feature) 0 +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# include + +// Defines default options for running iRODS with Address Sanitizer enabled. +// This is a convenience function which allows the iRODS server to start without +// having to specify options via environment variables. +extern "C" const char* __asan_default_options() +{ + // See root CMakeLists.txt file for definition. + return IRODS_ADDRESS_SANITIZER_DEFAULT_OPTIONS; +} // __asan_default_options +#endif + +namespace +{ +#ifdef __cpp_lib_filesystem + namespace fs = std::filesystem; +#else + namespace fs = boost::filesystem; +#endif + + using log_af = irods::experimental::log::agent_factory; + using log_agent = irods::experimental::log::agent; + + std::array g_proc_directory; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + std::time_t g_graceful_shutdown_timeout{}; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + + auto init_logger(pid_t _pid, bool _write_to_stdout, bool _enable_test_mode) -> void; + auto load_log_levels_for_loggers() -> void; + auto setup_signal_handlers() -> int; + auto createAndSetRECacheSalt() -> irods::error; + auto init_shared_memory_for_plugin(const nlohmann::json& _plugin_object) -> bool; + auto init_shared_memory_for_plugins() -> irods::error; + auto deinit_shared_memory_for_plugin(const nlohmann::json& _plugin_object) -> bool; + auto deinit_shared_memory_for_plugins() -> irods::error; + + // TODO Refactor these functions. + auto initServer(RsComm& _comm) -> int; + auto initServerMain(RsComm& _comm, const bool _enable_test_mode, const bool _write_to_stdout) -> int; + + auto handle_client_request(int _socket_fd, std::time_t _created_at) -> int; + auto cleanup() -> void; + auto cleanupAndExit(int status) -> void; + auto set_rule_engine_globals(RsComm& _comm) -> void; + auto agentMain(RsComm& _comm) -> int; + auto create_agent_pid_file_for_ips(const RsComm& _comm, std::time_t _created_at) -> void; + + auto reap_agent_processes(bool _shutting_down) -> void; +} // anonymous namespace + +auto main(int _argc, char* _argv[]) -> int +{ + ProcessType = AGENT_PT; // This process identifies itself as the agent factory or an agent. + + std::string hostname_cache_shm_name; + std::string dns_cache_shm_name; + bool write_to_stdout = false; + bool enable_test_mode = false; + + // TODO Boost.ProgramOptions isn't necessary. + + namespace po = boost::program_options; + + po::options_description opts_desc{""}; + + // clang-format off + opts_desc.add_options() + ("hostname-cache-shm-name,x", po::value(), "") + ("dns-cache-shm-name,y", po::value(), "") + ("stdout", po::bool_switch(&write_to_stdout), "") + ("test-mode,t", po::bool_switch(&enable_test_mode), ""); + // clang-format on + + po::positional_options_description pod; + pod.add("hostname-cache-shm-name", 1); + pod.add("dns-cache-shm-name", 1); + + try { + po::variables_map vm; + po::store(po::command_line_parser(_argc, _argv).options(opts_desc).positional(pod).run(), vm); + po::notify(vm); + + if (auto iter = vm.find("hostname-cache-shm-name"); std::end(vm) != iter) { + hostname_cache_shm_name = std::move(iter->second.as()); + } + else { + fmt::print(stderr, "Error: Missing [HOSTNAME_CACHE_SHM_NAME] parameter."); + return 1; + } + + if (auto iter = vm.find("dns-cache-shm-name"); std::end(vm) != iter) { + dns_cache_shm_name = std::move(iter->second.as()); + } + else { + fmt::print(stderr, "Error: Missing [DNS_CACHE_SHM_NAME] parameter."); + return 1; + } + } + catch (const std::exception& e) { + fmt::print(stderr, "Error: {}\n", e.what()); + return 1; + } + + try { + // Load configuration. + const auto config_file_path = irods::get_irods_config_directory() / "server_config.json"; + irods::server_properties::instance().init(config_file_path.c_str()); + irods::environment_properties::instance(); // Load the local environment file. + + // Initialize global pointer to ips data directory for agent cleanup. + // This is required so that the signal handler for reaping agents remains async-signal-safe. + { + const auto path = irods::get_irods_proc_directory().string(); + std::fill(std::begin(g_proc_directory), std::end(g_proc_directory), 0); + if (path.size() >= g_proc_directory.size()) { + log_af::error("{}: Proc directory size exceeds buffer size [{}].", __func__, g_proc_directory.size()); + return 1; + } + path.copy(g_proc_directory.data(), g_proc_directory.size()); + } + + // Capture the gracefule shutdown timeout value for agent cleanup. + // This is required so that the signal handler for reaping agents remains async-signal-safe. + { + const auto config_handle{irods::server_properties::instance().map()}; + const auto& config{config_handle.get_json()}; + g_graceful_shutdown_timeout = config.at("graceful_shutdown_timeout_in_seconds").get(); + } + + // TODO Consider removing the need for these along with all options. + // All logging should be controlled via the new logging system. + rodsLogLevel(LOG_NOTICE); + rodsLogSqlReq(0); + + init_logger(getppid(), write_to_stdout, enable_test_mode); + + log_af::info("{}: Initializing loggers for agent factory.", __func__); + load_log_levels_for_loggers(); + + log_af::info("{}: Initializing signal handlers for agent factory.", __func__); + if (setup_signal_handlers() == -1) { + log_af::error("{}: Error setting up signal handlers for agent factory process.", __func__); + return 1; + } + + log_af::info("{}: Initializing client allowlist for agent factory.", __func__); + irods::client_api_allowlist::init(); + + // Initialize shared memory systems. + log_af::info("{}: Initializing shared memory for agent factory.", __func__); + + irods::experimental::net::hostname_cache::init_no_create(hostname_cache_shm_name); + irods::experimental::net::dns_cache::init_no_create(dns_cache_shm_name); + + irods::experimental::replica_access_table::init(); + irods::at_scope_exit deinit_replica_access_table{[] { irods::experimental::replica_access_table::deinit(); }}; + + log_af::info("{}: Initializing zone information for agent factory.", __func__); + + // Set the default value for evicting DNS cache entries. + using key_path_t = irods::configuration_parser::key_path_t; + irods::set_server_property( + key_path_t{irods::KW_CFG_ADVANCED_SETTINGS, irods::KW_CFG_DNS_CACHE, irods::KW_CFG_EVICTION_AGE_IN_SECONDS}, + irods::get_dns_cache_eviction_age()); + // Set the default value for evicting hostname cache entries. + irods::set_server_property( + key_path_t{irods::KW_CFG_ADVANCED_SETTINGS, irods::KW_CFG_HOSTNAME_CACHE, irods::KW_CFG_EVICTION_AGE_IN_SECONDS}, + irods::get_hostname_cache_eviction_age()); + + if (const auto res = createAndSetRECacheSalt(); !res.ok()) { + log_af::error("{}: createAndSetRECacheSalt error.\n{}", __func__, res.result()); + return 1; + } + + if (const auto res = init_shared_memory_for_plugins(); !res.ok()) { + log_af::error("{}: Failed to initialize shared memory for plugins. [error code={}]", __func__, res.code()); + return 1; + } + irods::at_scope_exit remove_shared_memory{[pid_af = getpid()] { + // Only the agent factory is allowed to deinit the shared memory associated + // with plugins. This is especially important because the NREP's shared memory + // is shared by all agents running on the server. + // + // TODO Should the agent factory always do this? + // What happens if the agent factory terminates before a agent completes? + // The agent could be running policy that relies on the shared memory. + // Should the agent factory kill all agents immediately since the agents don't + // respond to SIGINT / SIGTERM immediately? + if (getpid() == pid_af) { + deinit_shared_memory_for_plugins(); + } + }}; + + // Initialize the global rule engine plugin facilities. + // This is where the rule engine plugins are loaded (i.e. plugin_factory is invoked here). + irods::re_plugin_globals = std::make_unique(); + + // Initialize zone information for request processing. + // initServerMain starts the listening socket and stores it in svrComm. + // TODO initServerMain can likely be simplified. + RsComm svrComm{}; // RsComm contains a std::string, so never use memset() on this type! + if (const auto ec = initServerMain(svrComm, false, false); ec < 0) { + log_af::error("{}: initServerMain error. [error code={}]", __func__, ec); + return 1; + } + + // Load server side pluggable api entries. + irods::api_entry_table& RsApiTable = irods::get_server_api_table(); + irods::pack_entry_table& ApiPackTable = irods::get_pack_table(); + if (const auto ret = irods::init_api_table(RsApiTable, ApiPackTable, false); !ret.ok()) { + log_af::error("{}: init_api_table error for server: {}", __func__, ret.user_result()); + return 1; + } + + // Load client side pluggable api entries. + irods::api_entry_table& RcApiTable = irods::get_client_api_table(); + if (const auto ret = irods::init_api_table(RcApiTable, ApiPackTable, false); !ret.ok()) { + log_af::error("{}: init_api_table error for server: {}", __func__, ret.user_result()); + return 1; + } + + // Run setup() for each rule engine plugin. + // This allows rule engine plugins to setup state which doesn't change frequently. + irods::re_plugin_globals->global_re_mgr.call_setup_operations(); + + // Enter parent process main loop. + // + // This process should never introduce threads. Everything it cares about must be handled + // within the loop. This keeps things simple and straight forward. + // + // THE PARENT PROCESS IS THE ONLY PROCESS THAT SHOULD/CAN REACT TO SIGNALS! + // EVERYTHING IS PROPAGATED THROUGH/FROM THE PARENT PROCESS! + + fd_set sockMask; + FD_ZERO(&sockMask); // NOLINT(readability-isolate-declaration) + + while (true) { + if (g_terminate) { + log_af::info("{}: Received shutdown instruction. Exiting agent factory main loop.", __func__); + break; + } + + if (g_terminate_graceful) { + log_af::info("{}: Received graceful shutdown instruction. Exiting agent factory main loop.", __func__); + break; + } + + // NOLINTNEXTLINE(hicpp-signed-bitwise, cppcoreguidelines-pro-bounds-constant-array-index) + FD_SET(svrComm.sock, &sockMask); + + int numSock = 0; + struct timeval time_out; // NOLINT(cppcoreguidelines-pro-type-member-init) + time_out.tv_sec = 0; + time_out.tv_usec = 500 * 1000; + const auto original_time_out = time_out; + + while ((numSock = select(svrComm.sock + 1, &sockMask, nullptr, nullptr, &time_out)) == -1) { + if (g_terminate) { + log_af::info("{}: Received shutdown instruction. Exiting agent factory select() loop.", __func__); + break; + } + + if (g_terminate_graceful) { + log_af::info("{}: Received graceful shutdown instruction. Exiting agent factory select() loop.", __func__); + break; + } + + // "select" modifies the timeval structure, so reset it. + time_out.tv_sec = 0; + time_out.tv_usec = 500 * 1000; + + if (EINTR == errno) { + log_af::trace("{}: select() interrupted", __func__); + // NOLINTNEXTLINE(hicpp-signed-bitwise, cppcoreguidelines-pro-bounds-constant-array-index) + FD_SET(svrComm.sock, &sockMask); + continue; + } + + log_af::error("{}: select() error, errno = {}", __func__, errno); + return 1; + } + + if (g_terminate) { + log_af::info("{}: Received shutdown instruction. Exiting agent factory main loop.", __func__); + break; + } + + if (g_terminate_graceful) { + log_af::info("{}: Received shutdown instruction. Exiting agent factory main loop.", __func__); + break; + } + + if (0 == numSock) { + // "select" modifies the timeval structure, so reset it. + time_out = original_time_out; + continue; + } + + const int newSock = rsAcceptConn(&svrComm); + if (newSock < 0) { + log_af::info("{}: rsAcceptConn() error, errno = {}", __func__, errno); + continue; + } + + // Fork agent to handle client request. + + const auto agent_pid = fork(); + if (agent_pid == 0) { + close(svrComm.sock); // Close the listening socket. + try { + return handle_client_request(newSock, std::time(nullptr)); + } + catch (const std::exception& e) { + log_agent::critical("{}: Exception caught in outer most scope of request handler: {}", __func__, e.what()); + return 1; + } + } + + if (agent_pid > 0) { + close(newSock); + } + else { + log_af::critical("Failed to fork agent. errno={}", errno); + } + } + + // Start shutting everything down. + + // Do not accept new client requests (i.e. close the listening socket). + close(svrComm.sock); + + // Give the rule engine plugins a chance to free any resources established during setup(). + irods::re_plugin_globals->global_re_mgr.call_teardown_operations(); + + if (0 == g_terminate_graceful) { + // Instruct all agents to shutdown gracefully. + // To avoid unnecessary complexity, we use SIGUSR1 as the termination signal for agents. + // Attempting to use SIGTERM would also notify the main server process and delay server. + kill(0, SIGUSR1); + } + + reap_agent_processes(true); + + log_af::info("{}: Shutdown complete.", __func__); + + return 0; + } + catch (const std::exception& e) { + fmt::print(stderr, "Error: {}\n", e.what()); + return 1; + } +} // main + +namespace +{ + auto init_logger(pid_t _pid, bool _write_to_stdout, bool _enable_test_mode) -> void + { + namespace logger = irods::experimental::log; + + logger::init(_pid, _write_to_stdout, _enable_test_mode); + log_af::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_AGENT_FACTORY)); + logger::set_server_type("agent_factory"); + logger::set_server_zone(irods::get_server_property(irods::KW_CFG_ZONE_NAME)); + logger::set_server_hostname(boost::asio::ip::host_name()); + } // init_logger + + auto load_log_levels_for_loggers() -> void + { + namespace logger = irods::experimental::log; + + logger::agent::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_AGENT)); + logger::agent_factory::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_AGENT_FACTORY)); + logger::api::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_API)); + logger::authentication::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_AUTHENTICATION)); + logger::database::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_DATABASE)); + logger::genquery2::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_GENQUERY2)); + logger::legacy::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_LEGACY)); + logger::microservice::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_MICROSERVICE)); + logger::network::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_NETWORK)); + logger::resource::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_RESOURCE)); + logger::rule_engine::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_RULE_ENGINE)); + logger::sql::set_level(logger::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_SQL)); + } // load_log_levels_for_loggers + + auto setup_signal_handlers() -> int + { + // DO NOT memset sigaction structures! + + std::signal(SIGINT, SIG_IGN); + + // SIGTERM + struct sigaction sa_terminate; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_terminate.sa_mask); + sa_terminate.sa_flags = SA_SIGINFO; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + sa_terminate.sa_sigaction = [](int, siginfo_t* _siginfo, void*) { + const auto saved_errno = errno; + + // Only respond to SIGTERM if the main server process sent it. + if (getppid() == _siginfo->si_pid && 0 == g_terminate_graceful) { + g_terminate = 1; + } + + errno = saved_errno; + }; + if (sigaction(SIGTERM, &sa_terminate, nullptr) == -1) { + return -1; + } + + // SIGQUIT (graceful shutdown) + struct sigaction sa_terminate_graceful; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_terminate_graceful.sa_mask); + sa_terminate_graceful.sa_flags = SA_SIGINFO; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + sa_terminate_graceful.sa_sigaction = [](int, siginfo_t* _siginfo, void*) { + const auto saved_errno = errno; + + // Only respond to SIGQUIT if the main server process sent it. + if (getppid() == _siginfo->si_pid && 0 == g_terminate) { + g_terminate_graceful = 1; + } + + errno = saved_errno; + }; + if (sigaction(SIGQUIT, &sa_terminate_graceful, nullptr) == -1) { + return -1; + } + + // SIGCHLD + struct sigaction sa_sigchld; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_sigchld.sa_mask); + sa_sigchld.sa_flags = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access, cppcoreguidelines-pro-type-cstyle-cast) + sa_sigchld.sa_handler = [](int) { + const auto saved_errno = errno; + reap_agent_processes(false); + errno = saved_errno; + }; + if (sigaction(SIGCHLD, &sa_sigchld, nullptr) == -1) { + return -1; + } + + irods::setup_unrecoverable_signal_handlers(); + + return 0; + } // setup_signal_handlers + + // We incorporate the cache salt into the rule engine's named_mutex and shared memory object. + // This prevents (most of the time) an orphaned mutex from halting server standup. Issue most often seen + // when a running iRODS installation is uncleanly killed (leaving the file system object used to implement + // boost::named_mutex e.g. in /var/run/shm) and then the iRODS user account is recreated, yielding a different + // UID. The new iRODS user account is then unable to unlock or remove the existing mutex, blocking the server. + auto createAndSetRECacheSalt() -> irods::error + { + // Should only ever set the cache salt once. + try { + const auto& existing_salt = irods::get_server_property(irods::KW_CFG_RE_CACHE_SALT); + log_af::debug("createAndSetRECacheSalt: Cache salt already set [{}]", existing_salt.c_str()); + return ERROR(SYS_ALREADY_INITIALIZED, "createAndSetRECacheSalt: Cache salt already set"); + } + catch (const irods::exception&) { + irods::buffer_crypt::array_t buf; + irods::error ret = irods::buffer_crypt::generate_key(buf, /* RE_CACHE_SALT_NUM_RANDOM_BYTES */ 40); + if (!ret.ok()) { + log_af::critical("createAndSetRECacheSalt: failed to generate random bytes"); + return PASS(ret); + } + + std::string cache_salt_random; + ret = irods::buffer_crypt::hex_encode(buf, cache_salt_random); + if (!ret.ok()) { + log_af::critical("createAndSetRECacheSalt: failed to hex encode random bytes"); + return PASS(ret); + } + + const auto cache_salt = fmt::format("pid{}_{}", static_cast(getpid()), cache_salt_random); + + try { + irods::set_server_property(irods::KW_CFG_RE_CACHE_SALT, cache_salt); + } + catch (const nlohmann::json::exception& e) { + log_af::critical("createAndSetRECacheSalt: failed to set server_properties"); + return ERROR(SYS_INVALID_INPUT_PARAM, e.what()); + } + catch (const std::exception&) {} + + if (setenv(SP_RE_CACHE_SALT, cache_salt.c_str(), 1) != 0) { + log_af::critical("createAndSetRECacheSalt: failed to set environment variable"); + return ERROR(SYS_SETENV_ERR, "createAndSetRECacheSalt: failed to set environment variable"); + } + + return SUCCESS(); + } + } // createAndSetRECacheSalt + + auto init_shared_memory_for_plugin(const nlohmann::json& _plugin_object) -> bool + { + const auto itr = _plugin_object.find(irods::KW_CFG_SHARED_MEMORY_INSTANCE); + + if (_plugin_object.end() != itr) { + const auto& mem_name = itr->get_ref(); + prepareServerSharedMemory(mem_name); + return true; + } + + return false; + } // init_shared_memory_for_plugin + + auto init_shared_memory_for_plugins() -> irods::error + { + try { + const auto config_handle{irods::server_properties::instance().map()}; + const auto& config{config_handle.get_json()}; + + for (const auto& item : config.at(irods::KW_CFG_PLUGIN_CONFIGURATION).items()) { + for (const auto& plugin : item.value().items()) { + init_shared_memory_for_plugin(plugin.value()); + } + } + } + catch (const irods::exception& e) { + return irods::error(e); + } + catch (const std::exception& e) { + return ERROR(SYS_INTERNAL_ERR, e.what()); + } + + return SUCCESS(); + } // init_shared_memory_for_plugins + + auto deinit_shared_memory_for_plugin(const nlohmann::json& _plugin_object) -> bool + { + const auto itr = _plugin_object.find(irods::KW_CFG_SHARED_MEMORY_INSTANCE); + + if (_plugin_object.end() != itr) { + const auto& mem_name = itr->get_ref(); + removeSharedMemory(mem_name); + resetMutex(mem_name.c_str()); + return true; + } + + return false; + } // deinit_shared_memory_for_plugin + + auto deinit_shared_memory_for_plugins() -> irods::error + { + try { + const auto config_handle{irods::server_properties::instance().map()}; + const auto& config{config_handle.get_json()}; + + for (const auto& item : config.at(irods::KW_CFG_PLUGIN_CONFIGURATION).items()) { + for (const auto& plugin : item.value().items()) { + deinit_shared_memory_for_plugin(plugin.value()); + } + } + } + catch (const irods::exception& e) { + return irods::error(e); + } + catch (const std::exception& e) { + return ERROR(SYS_INTERNAL_ERR, e.what()); + } + + return SUCCESS(); + } // deinit_shared_memory_for_plugins + + auto initServer(RsComm& _comm) -> int + { + if (const auto ec = initServerInfo(0, &_comm); ec < 0) { + log_af::info("{}: initServerInfo error, status = {}", __func__, ec); + return ec; + } + + // TODO Unnecessary? + //resc_mgr.print_local_resources(); + + printZoneInfo(); + + rodsServerHost_t* rodsServerHost{}; + if (const auto ec = getRcatHost(PRIMARY_RCAT, nullptr, &rodsServerHost); ec < 0 || !rodsServerHost) { + return ec; + } + + std::string svc_role; + if (const auto res = get_catalog_service_role(svc_role); !res.ok()) { + log_af::error("{}: Could not get server role. [error code={}]", __func__, res.code()); + return res.code(); + } + + if (LOCAL_HOST == rodsServerHost->localFlag) { + if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == svc_role) { + disconnectRcat(); + } + } + else if (rodsServerHost->conn) { + rcDisconnect(rodsServerHost->conn); + rodsServerHost->conn = nullptr; + } + + return 0; + } // initServer + + auto initServerMain(RsComm& _comm, + const bool enable_test_mode = false, + const bool write_to_stdout = false) -> int + { + std::memset(&_comm, 0, sizeof(RsComm)); + int status = getRodsEnv(&_comm.myEnv); + if (status < 0) { + log_af::error("{}: getRodsEnv error. status = {}", __func__, status); + return status; + } + + // TODO Verify this is reading from the correct location. + // Consider non-pkg installs and pkg installs. + setRsCommFromRodsEnv(&_comm); + + // TODO Verify this is okay to do in the agent factory before forking any agents. + // Need a script that stress tests the server. +#if 0 + // Load server API table so that API plugins which are needed to stand up the server are + // available for use. + irods::api_entry_table& RsApiTable = irods::get_server_api_table(); + irods::pack_entry_table& ApiPackTable = irods::get_pack_table(); + if (const auto err = irods::init_api_table(RsApiTable, ApiPackTable, false); !err.ok()) { + irods::log(PASS(err)); + return err.code(); + } + + // If this is a catalog service consumer, the client API table should be loaded so that + // client calls can be made to the catalog service provider as part of the server + // initialization process. + irods::api_entry_table& RcApiTable = irods::get_client_api_table(); + if (const auto err = irods::init_api_table(RcApiTable, ApiPackTable, false); !err.ok()) { + irods::log(PASS(err)); + return err.code(); + } + + status = initServer(_comm); + if (status < 0) { + log_af::error("{}: initServer error. status = {}", __func__, status); + return 1; + } +#endif + + int zone_port; + try { + zone_port = irods::get_server_property(irods::KW_CFG_ZONE_PORT); + } + catch (const irods::exception& e) { + irods::log(irods::error(e)); + return e.code(); + } + + _comm.sock = sockOpenForInConn(&_comm, &zone_port, nullptr, SOCK_STREAM); + if (_comm.sock < 0) { + log_af::error("{}: sockOpenForInConn error. status = {}", __func__, _comm.sock); + return _comm.sock; + } + + if (listen(_comm.sock, MAX_LISTEN_QUE) < 0) { + log_af::error("{}: listen failed, errno: {}", __func__, errno); + return SYS_SOCK_LISTEN_ERR; + } + + log_af::info("{}: Server Release version {} - API Version {} is up", __func__, RODS_REL_VERSION, RODS_API_VERSION); + + return 0; + } // initServerMain + + auto handle_client_request(int _socket_fd, std::time_t _created_at) -> int + { + // Setup signal handlers for agent. + + // SIGINT is ignored to protect the agents from being killed when running the + // server in the foreground (i.e. no daemonization). The described behavior is + // observed by running the server in the foreground, enabling the default behavior + // of SIGINT, and entering CTRL-C in the bash terminal. + std::signal(SIGINT, SIG_IGN); + + // SIGTERM is ignored to avoid unwanted termination of the agent during a full + // shutdown of the iRODS server. + std::signal(SIGTERM, SIG_IGN); + + { + // To avoid unnecessary complexity for configuration reload, shutdown, and signals, + // we use SIGUSR1 as the signal for instructing the agents to shutdown. + struct sigaction sa_terminate; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_terminate.sa_mask); + sa_terminate.sa_flags = SA_SIGINFO; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + sa_terminate.sa_sigaction = [](int, siginfo_t* _siginfo, void*) { + // Only respond to SIGUSR1 if the agent factory triggered it. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + if (getppid() == _siginfo->si_pid) { + g_terminate = 1; + } + }; + if (sigaction(SIGUSR1, &sa_terminate, nullptr) == -1) { + return -1; // TODO Use a better error code or log instead. + } + } + + // SIGPIPE is ignored so that agents can detect socket issues to other servers. + std::signal(SIGPIPE, SIG_IGN); + + // Let the registered signal handler reap children and remove the agent info files for ips. + std::signal(SIGCHLD, SIG_DFL); + + // Do not allow admins to trigger a configuration reload directly. They must send + // the signal to the main server process. + std::signal(SIGHUP, SIG_IGN); + + irods::experimental::log::set_server_type("agent"); + + // Artificially create a conn object in order to create a network object. + // This is gratuitous but necessary to maintain the consistent interface. + RcComm tmp_comm{}; + + irods::network_object_ptr net_obj; + irods::error ret = irods::network_factory(&tmp_comm, net_obj); + + if (!ret.ok() || !net_obj.get()) { + log_agent::error("{}: Failed to initialize network object for client request.", __func__); + return 1; + } + + net_obj->socket_handle(_socket_fd); + startupPack_t* startupPack = nullptr; + struct timeval tv; + tv.tv_sec = READ_STARTUP_PACK_TOUT_SEC; + tv.tv_usec = 0; + + if (const auto res = readStartupPack(net_obj, &startupPack, &tv); !res.ok()) { + log_agent::error("{}: readStartupPack failed, [error code={}]", __func__, res.code()); + sendVersion(net_obj, res.code(), 0, nullptr, 0); + mySockClose(net_obj->socket_handle()); + return 0; + } + + if (startupPack->connectCnt > /* MAX_SVR_SVR_CONNECT_CNT */ 7) { + log_agent::error("{}: Exceeded connect count [max=7, count={}].", __func__, startupPack->connectCnt); + sendVersion(net_obj, SYS_EXCEED_CONNECT_CNT, 0, nullptr, 0); + mySockClose(net_obj->socket_handle()); + std::free(startupPack); + return 0; + } + + if (std::strcmp(startupPack->option, RODS_HEARTBEAT_T) == 0) { + const char* heartbeat = RODS_HEARTBEAT_T; + const int heartbeat_length = std::strlen(heartbeat); + int bytes_to_send = heartbeat_length; + + while (bytes_to_send) { + const int bytes_sent = send(net_obj->socket_handle(), &(heartbeat[heartbeat_length - bytes_to_send]), bytes_to_send, 0); + const int errsav = errno; + if (errsav != EINTR) { + log_agent::error("{}: Socket error encountered during heartbeat; socket returned {}", + __func__, strerror(errsav)); + break; + } + + if (bytes_sent > 0) { + bytes_to_send -= bytes_sent; + } + } + + mySockClose(net_obj->socket_handle()); + std::free(startupPack); + return 0; + } + + if (startupPack->clientUser[0] != '\0') { + if (const auto ec = chkAllowedUser(startupPack->clientUser, startupPack->clientRodsZone); ec < 0) { + sendVersion(net_obj, ec, 0, nullptr, 0); + mySockClose(net_obj->socket_handle()); + std::free(startupPack); + return 0; + } + } + + // + // This is where we return to iRODS 4.3 logic. + // + + RsComm rsComm{}; + + // Originally set via an environment variable in initRsCommWithStartupPack in iRODS 4.2+. + rsComm.sock = _socket_fd; + + irods::experimental::log::set_error_object(&rsComm.rError); + irods::at_scope_exit release_error_stack{[] { irods::experimental::log::set_error_object(nullptr); }}; + + rsComm.thread_ctx = static_cast(std::malloc(sizeof(thread_context))); + + bool require_cs_neg = false; + int status = initRsCommWithStartupPack(&rsComm, startupPack, require_cs_neg); // This version just uses the startupPack. + + // manufacture a network object for comms + ret = irods::network_factory(&rsComm, net_obj); + if (!ret.ok()) { + log_agent::error(PASS(ret).result()); + } + + if (status < 0) { + log_agent::error("initRsCommWithStartupPack error: [{}]", status); + sendVersion(net_obj, status, 0, nullptr, 0); + cleanupAndExit(status); + } + + // TODO This is initialized during agent factory startup. Why do we create a new one after forking the agent? + //irods::re_plugin_globals.reset(new irods::global_re_plugin_mgr); + // TODO This line invokes the start operation of all rule engine plugins. If a plugin opens a resource + // that cannot be shared (e.g. a database connection), then we have to come up with another way of + // achieving our goal of no config changes taking effect until a config reload/restart. + // + // All we want to do is make it so that configuration is static on server startup. Can a pre-startup + // entry point help with that? It would be run by the agent factory and only setup state for later use. + // Things that are considered a limited resource would NOT be handled here. This would make reloading of + // plugin configuration easier because the pre-startup logic is independent and well-defined. However, that's + // not something we need to worry about since the reload logic for the server simply forks new processes. + irods::re_plugin_globals->global_re_mgr.call_start_operations(); + + // TODO Cannot assume /var/lib/irods. + status = getRodsEnv(&rsComm.myEnv); + + if (status < 0) { + log_agent::error("agentMain :: getRodsEnv failed"); + sendVersion(net_obj, SYS_AGENT_INIT_ERR, 0, nullptr, 0); + cleanupAndExit(status); + } + + std::string svc_role; + ret = get_catalog_service_role(svc_role); + if (!ret.ok()) { + log_agent::error(PASS(ret).result()); + return 1; + } + + // TODO Do we want to keep this? +#if 0 + if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == svc_role) { + if (std::strstr(rsComm.myEnv.rodsDebug, "CAT") != nullptr) { + chlDebug(rsComm.myEnv.rodsDebug); + } + } +#endif + + status = initAgent(RULE_ENGINE_TRY_CACHE, &rsComm); + + if (status < 0) { + log_agent::error("agentMain :: initAgent failed: {}", status); + sendVersion(net_obj, SYS_AGENT_INIT_ERR, 0, nullptr, 0); + cleanupAndExit(status); + } + + if (rsComm.clientUser.userName[0] != '\0') { + status = chkAllowedUser(rsComm.clientUser.userName, rsComm.clientUser.rodsZone); + + if (status < 0) { + sendVersion(net_obj, status, 0, nullptr, 0); + cleanupAndExit(status); + } + } + + // handle negotiations with the client regarding TLS if requested + // this scope block makes valgrind happy + { + std::string neg_results; + ret = irods::client_server_negotiation_for_server(net_obj, neg_results, require_cs_neg, &rsComm); + if (!ret.ok() || irods::CS_NEG_FAILURE == neg_results) { + // send a 'we failed to negotiate' message here?? + // or use the error stack rule engine thingie + log_agent::error(PASS(ret).result()); + sendVersion(net_obj, SERVER_NEGOTIATION_ERROR, 0, nullptr, 0); + cleanupAndExit(ret.code()); + } + else { + // copy negotiation results to comm for action by network objects + std::snprintf(rsComm.negotiation_results, sizeof(rsComm.negotiation_results), "%s", neg_results.c_str()); + } + } + + // send the server version and status as part of the protocol. Put + // rsComm.reconnPort as the status + ret = sendVersion(net_obj, status, rsComm.reconnPort, rsComm.reconnAddr, rsComm.cookie); + + if (!ret.ok()) { + log_agent::error(PASS(ret).result()); + sendVersion(net_obj, SYS_AGENT_INIT_ERR, 0, nullptr, 0); + cleanupAndExit(status); + } + + create_agent_pid_file_for_ips(rsComm, _created_at); + + // call initialization for network plugin as negotiated + irods::network_object_ptr new_net_obj; + ret = irods::network_factory(&rsComm, new_net_obj); + if (!ret.ok()) { + log_agent::error(PASS(ret).result()); + return 1; + } + + ret = sockAgentStart(new_net_obj); + if (!ret.ok()) { + log_agent::error(PASS(ret).result()); + return 1; + } + + const auto cleanup_and_free_rsComm_members = [&rsComm] { + cleanup(); + if (rsComm.thread_ctx) { + std::free(rsComm.thread_ctx); // NOLINT(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory) + } + if (rsComm.auth_scheme) { + std::free(rsComm.auth_scheme); // NOLINT(cppcoreguidelines-no-malloc, cppcoreguidelines-owning-memory) + } + }; + + new_net_obj->to_server(&rsComm); + status = agentMain(rsComm); + + // call initialization for network plugin as negotiated + ret = sockAgentStop( new_net_obj ); + if (!ret.ok()) { + log_agent::error(PASS(ret).result()); + cleanup_and_free_rsComm_members(); + return 1; + } + + new_net_obj->to_server(&rsComm); + + cleanup_and_free_rsComm_members(); + + // clang-format off + (0 == status) + ? log_agent::debug("Agent [{}] exiting with status = {}", getpid(), status) + : log_agent::error("Agent [{}] exiting with status = {}", getpid(), status); + // clang-format on + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) + // This function must be called here due to the use of _exit() (just below). Address Sanitizer (ASan) + // relies on std::atexit handlers to report its findings. _exit() does not trigger any of the handlers + // registered by ASan, therefore, we manually run ASan just before the agent exits. + __lsan_do_leak_check(); +#endif + + return (0 == status) ? 0 : 1; + } // handle_client_request + + auto cleanup() -> void + { + std::string svc_role; + irods::error ret = get_catalog_service_role(svc_role); + if (!ret.ok()) { + log_agent::error(PASS(ret).result()); + } + + if (INITIAL_DONE == InitialState) { + close_all_l1_descriptors(*ThisComm); + + // This agent's PID must be erased from all replica access table entries as it will soon no longer exist. + irods::experimental::replica_access_table::erase_pid(getpid()); + + irods::replica_state_table::deinit(); + + disconnectAllSvrToSvrConn(); + } + + if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == svc_role) { + disconnectRcat(); + } + + irods::re_plugin_globals->global_re_mgr.call_stop_operations(); + } // cleanup + + auto cleanupAndExit(int status) -> void + { + cleanup(); + + if (status >= 0) { + std::exit(0); // NOLINT(concurrency-mt-unsafe) + } + + std::exit(1); // NOLINT(concurrency-mt-unsafe) + } // cleanupAndExit + + auto set_rule_engine_globals(RsComm& _comm) -> void + { + irods::set_server_property(irods::CLIENT_USER_NAME_KW, _comm.clientUser.userName); + irods::set_server_property(irods::CLIENT_USER_ZONE_KW, _comm.clientUser.rodsZone); + irods::set_server_property(irods::CLIENT_USER_PRIV_KW, _comm.clientUser.authInfo.authFlag); + irods::set_server_property(irods::PROXY_USER_NAME_KW, _comm.proxyUser.userName); + irods::set_server_property(irods::PROXY_USER_ZONE_KW, _comm.proxyUser.rodsZone); + irods::set_server_property(irods::PROXY_USER_PRIV_KW, _comm.clientUser.authInfo.authFlag); + } // set_rule_engine_globals + + auto agentMain(RsComm& _comm) -> int + { + int status = 0; + + // compiler backwards compatibility hack + // see header file for more details + irods::dynamic_cast_hack(); + + while (status >= 0) { + if (g_terminate) { + log_agent::trace("{}: Received shutdown instruction. Agent is shutting down.", __func__); + break; + } + + // set default to the native auth scheme here. + if (!_comm.auth_scheme) { + _comm.auth_scheme = strdup("native"); + } + + // The following is an artifact of the legacy authentication plugins. This operation is + // only useful for certain plugins which are not supported in 4.3.0, so it is being + // left out of compilation for now. Once we have determined that this is safe to do in + // general, this section can be removed. +#if 0 + // construct an auth object based on the scheme specified in the comm + irods::auth_object_ptr auth_obj; + if (const auto err = irods::auth_factory(_comm.auth_scheme, &_comm.rError, auth_obj); !err.ok()) { + irods::experimental::api::plugin_lifetime_manager::destroy(); + + irods::log(PASSMSG(fmt::format( + "Failed to factory an auth object for scheme: \"{}\".", + _comm.auth_scheme), err)); + + return err.code(); + } + + irods::plugin_ptr ptr; + if (const auto err = auth_obj->resolve(irods::AUTH_INTERFACE, ptr); !err.ok()) { + irods::experimental::api::plugin_lifetime_manager::destroy(); + + irods::log(PASSMSG(fmt::format( + "Failed to resolve the auth plugin for scheme: \"{}\".", + _comm.auth_scheme), err)); + + return err.code(); + } + + irods::auth_ptr auth_plugin = boost::dynamic_pointer_cast(ptr); + + // Call agent start + if (const auto err = auth_plugin->call(_comm, irods::AUTH_AGENT_START, auth_obj, ""); !err.ok()) { + irods::experimental::api::plugin_lifetime_manager::destroy(); + + irods::log(PASSMSG(fmt::format( + "Failed during auth plugin agent start for scheme: \"{}\".", + _comm.auth_scheme), err)); + + return err.code(); + } +#endif + + // add the user info to the server properties for + // reach by the operation wrapper for access by the + // dynamic policy enforcement points + try { + set_rule_engine_globals(_comm); + } + catch (const irods::exception& e) { + log_agent::error("set_rule_engine_globals failed:\n{}", e.what()); + } + + if (_comm.ssl_do_accept) { + status = sslAccept(&_comm); + if (status < 0) { + log_agent::error("sslAccept failed in agentMain with status {}", status); + } + _comm.ssl_do_accept = 0; + } + if (_comm.ssl_do_shutdown) { + status = sslShutdown(&_comm); + if (status < 0) { + log_agent::error("sslShutdown failed in agentMain with status {}", status); + } + _comm.ssl_do_shutdown = 0; + } + + status = readAndProcClientMsg(&_comm, READ_HEADER_TIMEOUT); + if (status < 0) { + if (status == DISCONN_STATUS) { + status = 0; + break; + } + } + } + + irods::experimental::api::plugin_lifetime_manager::destroy(); + + // determine if we even need to connect, break the + // infinite reconnect loop. + if (!resc_mgr.need_maintenance_operations()) { + return status; + } + + // find the icat host + rodsServerHost_t* rodsServerHost = 0; + status = getRcatHost(PRIMARY_RCAT, 0, &rodsServerHost); + if (status < 0) { + log_agent::error(ERROR(status, "getRcatHost failed.").result()); + return status; + } + + // connect to the icat host + status = svrToSvrConnect(&_comm, rodsServerHost); + if ( status < 0 ) { + log_agent::error(ERROR(status, "svrToSvrConnect failed.").result()); + return status; + } + + // call post disconnect maintenance operations before exit + status = resc_mgr.call_maintenance_operations(rodsServerHost->conn); + + return status; + } // agentMain + + auto create_agent_pid_file_for_ips(const RsComm& _comm, std::time_t _created_at) -> void + { + std::string_view local_zone = "UNKNOWN"; + if (const auto* lz = getLocalZoneName(); lz) { + local_zone = lz; + } + + std::string_view client_zone = _comm.clientUser.rodsZone; + if (client_zone.empty()) { + client_zone = local_zone; + } + + std::string_view proxy_zone = _comm.proxyUser.rodsZone; + if (proxy_zone.empty()) { + proxy_zone = local_zone; + } + + std::string_view client_program_name = _comm.option; + if (client_program_name.empty()) { + client_program_name = "UNKNOWN"; + } + + const auto proc_dir = irods::get_irods_proc_directory(); + const auto pid_file = fmt::format("{}/{}", proc_dir.c_str(), getpid()); + + if (std::ofstream out{pid_file}; out) { + out << fmt::format("{} {} {} {} {} {} {}\n", + _comm.clientUser.userName, + client_zone, + _comm.proxyUser.userName, + proxy_zone, + client_program_name, + _comm.clientAddr, + static_cast(_created_at)); + } + else { + log_agent::error("{}: Could not open file [{}] for agent/ips data.", __func__, pid_file); + } + } // create_agent_pid_file_for_ips + + auto reap_agent_processes(bool _shutting_down) -> void + { + pid_t pid; + int status; + char agent_pid[16]; + char agent_pid_file_path[1024]; // TODO _POSIX_PATH_MAX? + + const auto unlink_agent_pid_file = [&](const pid_t _pid) { + auto [p, ec] = std::to_chars(agent_pid, agent_pid + sizeof(agent_pid), _pid); + if (std::errc{} != ec) { + return; + } + + // Add the null-terminating byte. + *p = 0; + + std::memset(agent_pid_file_path, 0, sizeof(agent_pid_file_path)); + std::strcpy(agent_pid_file_path, g_proc_directory.data()); + std::strcat(agent_pid_file_path, "/"); + std::strcat(agent_pid_file_path, agent_pid); + + unlink(agent_pid_file_path); + }; + + if (g_terminate_graceful && _shutting_down) { + const auto start_time = std::time(nullptr); + + // Poll termination of agents until one of the following conditions is true. + // - waitpid() returns -1 and errno is ECHILD + // - The graceful timeout limit has been exceeded + // + // If all agents have exited, break out the loop (i.e. we're done). + // If the graceful timeout limit has been exceeded, send SIGUSR1 signal to all agents + // so they terminate immediately. + while (true) { + pid = waitpid(-1, &status, WNOHANG); + + // This branch is for when we're shutting down (i.e. not in a signal handler). + // That is - continue to loop until all agents have terminated. + if (-1 == pid && ECHILD == errno) { + // All agents have terminated. + return; + } + + if (pid > 0) { + unlink_agent_pid_file(pid); + } + + if ((std::time(nullptr) - start_time) >= g_graceful_shutdown_timeout) { + // Instruct agents to terminate. Breaking out the loop and going into the + // normal reaping of agents is necessary because the files generated for ips + // must be cleaned up. + kill(0, SIGUSR1); + break; + } + } + } + + const auto flags = _shutting_down ? 0: WNOHANG; + + while (true) { + pid = waitpid(-1, &status, flags); + + // This branch is for when we're shutting down (i.e. not in a signal handler). + // That is - continue to loop until all agents have terminated. + if (_shutting_down) { + if (-1 == pid && ECHILD == errno) { + // All agents have terminated. + break; + } + } + // This branch is for when we're in a signal handler. + // That is - continue to loop as long as waitpid returns a valid pid. + else if (pid <= 0) { + break; + } + + unlink_agent_pid_file(pid); + } + } // reap_agent_processes +} // anonymous namespace diff --git a/server/main_server/src/main.cpp b/server/main_server/src/main.cpp new file mode 100644 index 0000000000..33a4006eb7 --- /dev/null +++ b/server/main_server/src/main.cpp @@ -0,0 +1,1153 @@ +#include "irods/catalog.hpp" +#include "irods/client_connection.hpp" +#include "irods/dns_cache.hpp" +#include "irods/get_grid_configuration_value.h" +#include "irods/hostname_cache.hpp" +#include "irods/irods_at_scope_exit.hpp" +#include "irods/irods_client_api_table.hpp" +#include "irods/irods_configuration_keywords.hpp" +#include "irods/irods_default_paths.hpp" +#include "irods/irods_environment_properties.hpp" +#include "irods/irods_logger.hpp" +#include "irods/irods_server_api_table.hpp" +#include "irods/irods_server_properties.hpp" +#include "irods/irods_signal.hpp" +#include "irods/irods_version.h" +#include "irods/plugins/api/delay_server_migration_types.h" +#include "irods/plugins/api/grid_configuration_types.h" +#include "irods/rcConnect.h" // For RcComm +#include "irods/rcGlobalExtern.h" // For ProcessType +#include "irods/rcMisc.h" +#include "irods/rodsClient.h" +#include "irods/rodsErrorTable.h" +#include "irods/set_delay_server_migration_info.h" + +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef __cpp_lib_filesystem +# include +#else +# include +#endif + +// __has_feature is a Clang specific feature. +// The preprocessor code below exists so that other compilers can be used (e.g. GCC). +#ifndef __has_feature +# define __has_feature(feature) 0 +#endif + +#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) +# include + +// Defines default options for running iRODS with Address Sanitizer enabled. +// This is a convenience function which allows the iRODS server to start without +// having to specify options via environment variables. +extern "C" const char* __asan_default_options() +{ + // See root CMakeLists.txt file for definition. + return IRODS_ADDRESS_SANITIZER_DEFAULT_OPTIONS; +} // __asan_default_options +#endif + +namespace +{ +#ifdef __cpp_lib_filesystem + namespace fs = std::filesystem; +#else + namespace fs = boost::filesystem; +#endif + + namespace log_ns = irods::experimental::log; + + using log_server = irods::experimental::log::server; + + volatile std::sig_atomic_t g_terminate = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + volatile std::sig_atomic_t g_terminate_graceful = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + volatile std::sig_atomic_t g_reload_config = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + + // This global variable MUST always hold the max number of child processes forkable + // by the main server process. It guarantees that the main server process reaps children + // it's lost due to desync issues stemming from fork(), exec() and kill(). + const int g_max_number_of_child_processes = 2; + + pid_t g_pid_af = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + pid_t g_pid_ds = 0; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) + + auto print_usage() -> void; + auto print_version_info() -> void; + auto print_configuration_template() -> void; + + auto validate_configuration() -> bool; + auto daemonize() -> void; + auto create_pid_file(const std::string& _pid_file) -> int; + auto init_logger(pid_t _pid, bool _write_to_stdout, bool _enable_test_mode) -> void; + auto check_catalog_schema_version() -> std::pair; + auto setup_signal_handlers() -> int; + + auto handle_shutdown() -> void; + auto handle_shutdown_graceful() -> void; + + auto set_delay_server_migration_info(RcComm& _comm, + std::string_view _leader, + std::string_view _successor) -> void; + auto get_delay_server_leader(RcComm& _comm) -> std::optional; + auto get_delay_server_successor(RcComm& _comm) -> std::optional; + auto launch_agent_factory(const char* _src_func, bool _write_to_stdout, bool _enable_test_mode) -> bool; + auto handle_configuration_reload(bool _write_to_stdout, bool _enable_test_mode) -> void; + auto launch_delay_server(bool _write_to_stdout, bool _enable_test_mode) -> void; + auto migrate_and_launch_delay_server(bool _write_to_stdout, + bool _enable_test_mode, + bool& _first_boot, + std::chrono::steady_clock::time_point& _time_start) -> void; + auto log_stacktrace_files() -> void; + auto remove_leftover_agent_info_files_for_ips() -> void; +} // anonymous namespace + +auto main(int _argc, char* _argv[]) -> int +{ + [[maybe_unused]] const auto boot_time = std::chrono::system_clock::now(); + + bool write_to_stdout = false; + bool enable_test_mode = false; + + ProcessType = SERVER_PT; // This process identifies itself as a server. + + set_ips_display_name("irodsServer"); + + namespace po = boost::program_options; + + po::options_description opts_desc{""}; + + // clang-format off + opts_desc.add_options() + ("daemonize,d", "") + ("pid-file,p", po::value(), "") + ("stdout", po::bool_switch(&write_to_stdout), "") + ("test-mode,t", po::bool_switch(&enable_test_mode), "") + ("help,h", "") + ("version,v", ""); + // clang-format on + + try { + po::variables_map vm; + po::store(po::command_line_parser(_argc, _argv).options(opts_desc).run(), vm); + po::notify(vm); + + if (vm.count("help") > 0) { + print_usage(); + return 0; + } + + if (vm.count("version") > 0) { + print_version_info(); + return 0; + } + + if (vm.count("daemonize") > 0) { + daemonize(); + } + + std::string pid_file = (irods::get_irods_runstate_directory() / "irods/irods-server.pid").string(); + if (const auto iter = vm.find("pid-file"); std::end(vm) != iter) { + pid_file = std::move(iter->second.as()); + } + + if (create_pid_file(pid_file) != 0) { + fmt::print(stderr, "Error: could not create PID file [{}].\n", pid_file); + return 1; + } + } + catch (const std::exception& e) { + fmt::print(stderr, "Error: {}\n", e.what()); + return 1; + } + + try { + if (!validate_configuration()) { + return 1; + } + + const auto config_file_path = irods::get_irods_config_directory() / "server_config.json"; + irods::server_properties::instance().init(config_file_path.c_str()); + irods::environment_properties::instance(); // Load the local environment file. + + // TODO Consider removing the need for these along with all options. + // All logging should be controlled via the new logging system. + rodsLogLevel(LOG_NOTICE); + rodsLogSqlReq(0); + + init_logger(getpid(), write_to_stdout, enable_test_mode); + + if (const auto [up_to_date, db_vers] = check_catalog_schema_version(); !up_to_date) { + const auto msg = fmt::format("Catalog schema version mismatch: expected [{}], found [{}]", IRODS_CATALOG_SCHEMA_VERSION, db_vers); + log_server::critical(msg); + fmt::print(fmt::runtime(msg)); + return 1; + } + + // Setting up signal handlers here removes the need for reacting to shutdown signals + // such as SIGINT and SIGTERM during the startup sequence. + if (setup_signal_handlers() == -1) { + log_server::error("{}: Error setting up signal handlers for main server process.", __func__); + return 1; + } + + log_server::info("{}: Initializing shared memory for main server process.", __func__); + + namespace hnc = irods::experimental::net::hostname_cache; + hnc::init("irods_hostname_cache", irods::get_hostname_cache_shared_memory_size()); + irods::at_scope_exit deinit_hostname_cache{[] { hnc::deinit(); }}; + + namespace dnsc = irods::experimental::net::dns_cache; + dnsc::init("irods_dns_cache", irods::get_dns_cache_shared_memory_size()); + irods::at_scope_exit deinit_dns_cache{[] { dnsc::deinit(); }}; + + // Load server API table so that API plugins which are needed to stand up the server are + // available for use. + auto& server_api_table = irods::get_server_api_table(); + auto& pack_table = irods::get_pack_table(); + if (const auto res = irods::init_api_table(server_api_table, pack_table, false); !res.ok()) { + log_server::error("{}: {}", __func__, res.result()); + return 1; + } + + // If this is a catalog service consumer, the client API table should be loaded so that + // client calls can be made to the catalog service provider as part of the server + // initialization process. + auto& client_api_table = irods::get_client_api_table(); + if (const auto res = irods::init_api_table(client_api_table, pack_table, false); !res.ok()) { + log_server::error("{}: {}", __func__, res.result()); + return 1; + } + + if (!launch_agent_factory(__func__, write_to_stdout, enable_test_mode)) { + return 1; + } + + // Enter parent process main loop. + // + // This process should never introduce threads. Everything it cares about must be handled + // within the loop. This keeps things simple and straight forward. + // + // THE PARENT PROCESS IS THE ONLY PROCESS THAT SHOULD/CAN REACT TO SIGNALS! + // EVERYTHING IS PROPAGATED THROUGH/FROM THE PARENT PROCESS! + + // dsm = Short for delay server migration + // This is used to control the frequency of the delay server migration logic. + auto dsm_time_start = std::chrono::steady_clock::now(); + + // TODO Remove this and just delay the startup of the delay server. Even better if the delay server + // only starts up after the agent factory is accepting connections. + auto first_boot = true; + + while (true) { + if (g_terminate) { + log_server::info("{}: Received shutdown instruction. Exiting server main loop.", __func__); + handle_shutdown(); + break; + } + + if (g_terminate_graceful) { + log_server::info("{}: Received graceful shutdown instruction. Exiting server main loop.", __func__); + handle_shutdown_graceful(); + break; + } + + if (g_reload_config) { + handle_configuration_reload(write_to_stdout, enable_test_mode); + } + + // Clean up any zombie child processes if they exist. These appear following a configuration + // reload. We call waitpid() multiple times because the main server processes may have multiple + // child processes. + for (int i = 0; i < g_max_number_of_child_processes; ++i) { + waitpid(-1, nullptr, WNOHANG); + } + + log_stacktrace_files(); + remove_leftover_agent_info_files_for_ips(); + launch_agent_factory(__func__, write_to_stdout, enable_test_mode); + migrate_and_launch_delay_server(write_to_stdout, enable_test_mode, first_boot, dsm_time_start); + + std::this_thread::sleep_for(std::chrono::seconds{1}); + } + + log_server::info("{}: Server shutdown complete.", __func__); + + return 0; + } + catch (const std::exception& e) { + fmt::print(stderr, "Error: {}\n", e.what()); + return 1; + } +} // main + +namespace +{ + auto print_usage() -> void + { + // TODO Update help text. + fmt::print( +R"__(irodsServer - Launch an iRODS server + +Usage: irodsServer [OPTION]... + +TODO More words ... + +Mandatory arguments to long options are mandatory for short options too. + +Options: + -d, --daemonize + Run server instance in the background as a service. + -p, --pid-file FILE + The absolute path to FILE, which will be used as the + PID file for the server instance. + -h, --help Display this help message and exit. + -v, --version Display version information and exit. +)__"); + } // print_usage + + auto print_version_info() -> void + { + constexpr const auto commit = std::string_view{IRODS_GIT_COMMIT}.substr(0, 7); + fmt::print("irodsServer v{}.{}.{}-{}\n", IRODS_VERSION_MAJOR, IRODS_VERSION_MINOR, IRODS_VERSION_PATCHLEVEL, commit); + } // print_version_info + + auto validate_configuration() -> bool + { + try { + namespace jsonschema = jsoncons::jsonschema; + + std::ifstream config_file{(irods::get_irods_config_directory() / "server_config.json").c_str()}; + if (!config_file) { + return false; + } + const auto config = jsoncons::json::parse(config_file); + + // NOLINTNEXTLINE(bugprone-lambda-function-name) + const auto do_validate = [fn = __func__](const auto& _config, const std::string& _schema_file) { + const auto resolver = [](const jsoncons::uri& _uri) { + fmt::print("uri = [{}], path = [{}]\n", _uri.string(), _uri.path()); + + std::ifstream in{(irods::get_irods_home_directory() / _uri.path()).c_str()}; + if (!in) { + return jsoncons::json::null(); + } + + return jsoncons::json::parse(in); + }; + + fmt::print("{}: JSON schema file = [{}].\n", fn, _schema_file); + std::ifstream in{_schema_file}; + if (!in) { + return false; + } + const auto schema = jsoncons::json::parse(in); // The stream object cannot be instantiated inline. + const auto compiled = jsonschema::make_json_schema(schema, resolver); + + jsoncons::json_decoder decoder; + compiled.validate(_config, decoder); + const auto json_result = decoder.get_result(); + + if (!json_result.empty()) { + std::ostringstream out; + out << pretty_print(json_result); + fmt::print("{}: {}\n", fn, out.str()); + return false; + } + + return true; + }; // do_validate + + // Validate the server configuration. If that succeeds, move on to validating the + // irods_environment.json file. + const auto schema_dir = irods::get_irods_home_directory() / fmt::format("configuration_schemas/v{}", IRODS_CONFIGURATION_SCHEMA_VERSION); + const auto server_config_schema = schema_dir / "server_config.json"; + if (do_validate(config, server_config_schema.c_str())) { + std::string env_file; + std::string session_file; + if (const auto err = irods::get_json_environment_file(env_file, session_file); !err.ok()) { + fmt::print("{}: {}\n", __func__, err.status()); + return false; + } + + // Validate the irods_environment.json file referenced by the server configuration. + std::ifstream in{env_file}; + if (!in) { + return false; + } + const auto env_file_config = jsoncons::json::parse(in); + const auto svc_acct_schema = schema_dir / "service_account_environment.json"; + return do_validate(env_file_config, svc_acct_schema.c_str()); + } + } + catch (const std::exception& e) { + fmt::print("{}: {}\n", __func__, e.what()); + } + + return false; + } // validate_configuration + + auto daemonize() -> void + { + // Become a background process. + switch (fork()) { + case -1: _exit(1); + case 0: break; + default: _exit(0); + } + + // Become session leader. + if (setsid() == -1) { + _exit(1); + } + + // Make sure we aren't the session leader. + switch (fork()) { + case -1: _exit(1); + case 0: break; + default: _exit(0); + } + + umask(0); + + // Change the working directory to the root directory. Using the root directory is + // common and protects the OS by allowing it to unmount file systems. If the working + // directory of the daemon existed on a file system other than the one containing "/", + // the OS would not be able to unmount it. + // + // It's also possible to change the working directory to other directories, for example, + // the iRODS home directory. However, that directory normally exists under /var. + chdir("/"); + + // Get max number of open file descriptors. + auto max_fd = sysconf(_SC_OPEN_MAX); + if (-1 == max_fd) { + // Indeterminate, so take a guess. + max_fd = 8192; + } + + // Close open file descriptors. + for (auto fd = 0; fd < max_fd; ++fd) { + close(fd); + } + + // clang-format off + constexpr auto fd_stdin = 0; + constexpr auto fd_stdout = 1; + constexpr auto fd_stderr = 2; + // clang-format on + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + auto fd = open("/dev/null", O_RDWR); + if (fd_stdin != fd) { + _exit(1); + } + + if (dup2(fd, fd_stdout) != fd_stdout) { + _exit(1); + } + + if (dup2(fd, fd_stderr) != fd_stderr) { + _exit(1); + } + } // daemonize + + auto create_pid_file(const std::string& _pid_file) -> int + { + // Open the PID file. If it does not exist, create it and give the owner + // permission to read and write to it. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-signed-bitwise) + const auto fd = open(_pid_file.c_str(), O_RDWR | O_CREAT, S_IRUSR | S_IWUSR); + if (fd == -1) { + fmt::print("Could not open PID file.\n"); + return 1; + } + + // Get the current open flags for the open file descriptor. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + const auto flags = fcntl(fd, F_GETFD); + if (flags == -1) { + fmt::print("Could not retrieve open flags for PID file.\n"); + return 1; + } + + // Enable the FD_CLOEXEC option for the open file descriptor. + // This option will cause successful calls to exec() to close the file descriptor. + // Keep in mind that record locks are NOT inherited by forked child processes. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg, hicpp-signed-bitwise) + if (fcntl(fd, F_SETFD, flags | FD_CLOEXEC) == -1) { + fmt::print("Could not set FD_CLOEXEC on PID file.\n"); + return 1; + } + + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-member-init) + struct flock input; + input.l_type = F_WRLCK; + input.l_whence = SEEK_SET; + input.l_start = 0; + input.l_len = 0; + + // Try to acquire the write lock on the PID file. If we cannot get the lock, + // another instance of the application must already be running or something + // weird is going on. + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-vararg) + if (fcntl(fd, F_SETLK, &input) == -1) { + if (EAGAIN == errno || EACCES == errno) { + fmt::print("Could not acquire write lock for PID file. Another instance " + "could be running already.\n"); + return 1; + } + } + + if (ftruncate(fd, 0) == -1) { + fmt::print("Could not truncate PID file's contents.\n"); + return 1; + } + + const auto contents = fmt::format("{}\n", getpid()); + // NOLINTNEXTLINE(google-runtime-int) + if (write(fd, contents.data(), contents.size()) != static_cast(contents.size())) { + fmt::print("Could not write PID to PID file.\n"); + return 1; + } + + return 0; + } // create_pid_file + + auto init_logger(pid_t _pid, bool _write_to_stdout, bool _enable_test_mode) -> void + { + log_ns::init(_pid, _write_to_stdout, _enable_test_mode); + log_server::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_SERVER)); + log_ns::set_server_type("server"); + log_ns::set_server_zone(irods::get_server_property(irods::KW_CFG_ZONE_NAME)); + log_ns::set_server_hostname(boost::asio::ip::host_name()); + } // init_logger + + auto check_catalog_schema_version() -> std::pair + { + try { + const auto role = irods::get_server_property(irods::KW_CFG_CATALOG_SERVICE_ROLE); + + if (role == irods::KW_CFG_SERVICE_ROLE_PROVIDER) { + auto [db_instance, db_conn] = irods::experimental::catalog::new_database_connection(); + + auto row = nanodbc::execute(db_conn, "select option_value from R_GRID_CONFIGURATION where namespace = 'database' and option_name = 'schema_version'"); + if (!row.next()) { + return {false, -1}; + } + + const auto vers = row.get(0); + return {vers == IRODS_CATALOG_SCHEMA_VERSION, vers}; + } + + if (role == irods::KW_CFG_SERVICE_ROLE_CONSUMER) { + // TODO What should the consumer do? It doesn't have database credentials. + // Should all consumers have database credentials in iRODS 5? + } + } + catch (const irods::exception& e) { + log_server::error("{}: Could not verify catalog schema version: {}\n", __func__, e.client_display_what()); + } + catch (const std::exception& e) { + log_server::error("{}: Could not verify catalog schema version. Is the catalog service role defined in server_config.json?\n", __func__); + } + + return {false, -1}; + } // check_catalog_schema_version + + auto setup_signal_handlers() -> int + { + // DO NOT memset sigaction structures! + + std::signal(SIGUSR1, SIG_IGN); // NOLINT(cppcoreguidelines-pro-type-cstyle-cast) + + // SIGINT + struct sigaction sa_terminate; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_terminate.sa_mask); + sa_terminate.sa_flags = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + sa_terminate.sa_handler = [](int) { + // Only respond if the server hasn't been instructed to terminate already. + if (0 == g_terminate_graceful) { + g_terminate = 1; + } + }; + if (sigaction(SIGINT, &sa_terminate, nullptr) == -1) { + return -1; + } + + // SIGTERM + if (sigaction(SIGTERM, &sa_terminate, nullptr) == -1) { + return -1; + } + + // SIGQUIT (graceful shutdown) + struct sigaction sa_terminate_graceful; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_terminate_graceful.sa_mask); + sa_terminate_graceful.sa_flags = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + sa_terminate_graceful.sa_handler = [](int) { + // Only respond if the server hasn't been instructed to terminate already. + if (0 == g_terminate) { + g_terminate_graceful = 1; + } + }; + if (sigaction(SIGQUIT, &sa_terminate_graceful, nullptr) == -1) { + return -1; + } + + // SIGHUP + struct sigaction sa_sighup; // NOLINT(cppcoreguidelines-pro-type-member-init) + sigemptyset(&sa_sighup.sa_mask); + sa_sighup.sa_flags = 0; + // NOLINTNEXTLINE(cppcoreguidelines-pro-type-union-access) + sa_sighup.sa_handler = [](int) { g_reload_config = 1; }; + if (sigaction(SIGHUP, &sa_sighup, nullptr) == -1) { + return -1; + } + + irods::setup_unrecoverable_signal_handlers(); + + return 0; + } // setup_signal_handlers + + auto handle_shutdown() -> void + { + kill(g_pid_af, SIGTERM); + + if (g_pid_ds > 0) { + kill(g_pid_ds, SIGTERM); + } + + waitpid(g_pid_af, nullptr, 0); + log_server::info("{}: Agent Factory shutdown complete.", __func__); + + if (g_pid_ds > 0) { + waitpid(g_pid_ds, nullptr, 0); + log_server::info("{}: Delay Server shutdown complete.", __func__); + } + } // handle_shutdown + + auto handle_shutdown_graceful() -> void + { + kill(g_pid_af, SIGQUIT); + + if (g_pid_ds > 0) { + kill(g_pid_ds, SIGTERM); + } + + waitpid(g_pid_af, nullptr, 0); + log_server::info("{}: Agent Factory shutdown complete.", __func__); + + if (g_pid_ds > 0) { + waitpid(g_pid_ds, nullptr, 0); + log_server::info("{}: Delay Server shutdown complete.", __func__); + } + } // handle_shutdown_graceful + + auto get_delay_server_leader(RcComm& _comm) -> std::optional + { + GridConfigurationInput input{}; + std::strcpy(input.name_space, "delay_server"); + std::strcpy(input.option_name, "leader"); + + GridConfigurationOutput* output{}; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + irods::at_scope_exit free_output{[&output] { std::free(output); }}; + + if (const auto ec = rc_get_grid_configuration_value(&_comm, &input, &output); ec < 0) { + log_server::error( + "Could not retrieve delay server migration information from catalog " + "[error_code={}, namespace=delay_server, option_name=leader].", + ec); + return std::nullopt; + } + + return output->option_value; + } // get_delay_server_leader + + auto get_delay_server_successor(RcComm& _comm) -> std::optional + { + GridConfigurationInput input{}; + std::strcpy(input.name_space, "delay_server"); + std::strcpy(input.option_name, "successor"); + + GridConfigurationOutput* output{}; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory,cppcoreguidelines-no-malloc) + irods::at_scope_exit free_output{[&output] { std::free(output); }}; + + if (const auto ec = rc_get_grid_configuration_value(&_comm, &input, &output); ec < 0) { + log_server::error( + "Could not retrieve delay server migration information from catalog " + "[error_code={}, namespace=delay_server, option_name=successor].", + ec); + return std::nullopt; + } + + return output->option_value; + } // get_delay_server_successor + + auto set_delay_server_migration_info(RcComm& _comm, + std::string_view _leader, + std::string_view _successor) -> void + { + DelayServerMigrationInput input{}; + _leader.copy(input.leader, sizeof(DelayServerMigrationInput::leader)); + _successor.copy(input.successor, sizeof(DelayServerMigrationInput::successor)); + + if (const auto ec = rc_set_delay_server_migration_info(&_comm, &input); ec < 0) { + log_server::error( + "Failed to set delay server migration info in R_GRID_CONFIGURATION " + "[error_code={}, leader={}, successor={}].", + ec, + _leader, + _successor); + } + } // set_delay_server_migration_info + + auto launch_agent_factory(const char* _src_func, bool _write_to_stdout, bool _enable_test_mode) -> bool + { + auto launch = (0 == g_pid_af); + + if (g_pid_af > 0) { + if (const auto ec = kill(g_pid_af, 0); ec == -1) { + if (EPERM == errno || ESRCH == errno) { + launch = true; + g_pid_af = 0; + } + } + } + + if (!launch) { + return false; + } + + log_server::info("{}: Launching Agent Factory.", _src_func); + + // If we're planning on calling one of the functions from the exec-family, + // then we're only allowed to use async-signal-safe functions following the call + // to fork(). To avoid potential issues, we build up the argument list before + // doing the fork-exec. + + auto binary = (irods::get_irods_sbin_directory() / "irodsAgent").string(); + std::string hn_shm_name{irods::experimental::net::hostname_cache::shared_memory_name()}; + std::string dns_shm_name{irods::experimental::net::dns_cache::shared_memory_name()}; + + std::vector args{binary.data(), hn_shm_name.data(), dns_shm_name.data()}; + + if (_write_to_stdout) { + args.push_back("--stdout"); + } + + if (_enable_test_mode) { + args.push_back("-t"); + } + + args.push_back(nullptr); + + g_pid_af = fork(); + + if (0 == g_pid_af) { + execv(args[0], args.data()); + + // If execv() fails, the POSIX standard recommends using _exit() instead of exit() to avoid + // flushing stdio buffers and handlers registered by the parent. + // + // In the case of C++, this is necessary to avoid triggering destructors. Triggering a destructor + // could result in assertions made by the struct/class being violatied. For some data types, + // violating an assertion results in program termination (i.e. SIGABRT). + _exit(1); + } + else if (-1 == g_pid_af) { + g_pid_af = 0; + log_server::error("{}: Could not launch agent factory.", __func__); + return false; + } + + log_server::info("{}: Agent Factory PID = [{}].", __func__, g_pid_af); + return true; + } // launch_agent_factory + + auto handle_configuration_reload(bool _write_to_stdout, bool _enable_test_mode) -> void + { + log_server::info("{}: Received configuration reload instruction. Reloading configuration.", __func__); + + if (!validate_configuration()) { + log_server::error("{}: Invalid configuration. Continuing to run with previous configuration.", __func__); + return; + } + + if (g_pid_ds > 0) { + log_server::info("{}: Sending SIGTERM to delay server.", __func__); + kill(g_pid_ds, SIGTERM); + } + + log_server::info("{}: Sending SIGQUIT to agent factory.", __func__); + kill(g_pid_af, SIGQUIT); + + // Reset this variable so that the delay server migration logic can handle + // the relaunching of the delay server for us. + g_pid_ds = 0; + + try { + log_server::info("{}: Reloading configuration for main server process.", __func__); + irods::server_properties::instance().reload(); + irods::environment_properties::instance().capture(); + + // Update the logger for the main server process. + log_server::set_level(log_ns::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_SERVER)); + log_ns::set_server_zone(irods::get_server_property(irods::KW_CFG_ZONE_NAME)); + log_ns::set_server_hostname(boost::asio::ip::host_name()); + } + catch (const std::exception& e) { + log_server::error("{}: Error reloading configuration for main server process: {}", __func__, e.what()); + } + + // Launch a new agent factory to serve client requests. + // The previous agent factory is allowed to linger around until its children terminate. + launch_agent_factory(__func__, _write_to_stdout, _enable_test_mode); + + // We do not need to manually launch the delay server because the delay server migration + // logic will handle that for us. + + g_reload_config = 0; + } // handle_configuration_reload + + auto launch_delay_server(bool _write_to_stdout, bool _enable_test_mode) -> void + { + auto launch = (0 == g_pid_ds); + + if (g_pid_ds > 0) { + if (const auto ec = kill(g_pid_ds, 0); ec == -1) { + if (EPERM == errno || ESRCH == errno) { + launch = true; + g_pid_ds = 0; + } + } + } + + if (launch) { + log_server::info("{}: Launching Delay Server.", __func__); + + // If we're planning on calling one of the functions from the exec-family, + // then we're only allowed to use async-signal-safe functions following the call + // to fork(). To avoid potential issues, we build up the argument list before + // doing the fork-exec. + + auto binary = (irods::get_irods_sbin_directory() / "irodsDelayServer").string(); + std::vector args{binary.data()}; + + if (_write_to_stdout) { + args.push_back("--stdout"); + } + + if (_enable_test_mode) { + args.push_back("-t"); + } + + args.push_back(nullptr); + + g_pid_ds = fork(); + + if (0 == g_pid_ds) { + execv(args[0], args.data()); + + // If execv() fails, the POSIX standard recommends using _exit() instead of exit() to avoid + // flushing stdio buffers and handlers registered by the parent. + // + // In the case of C++, this is necessary to avoid triggering destructors. Triggering a destructor + // could result in assertions made by the struct/class being violatied. For some data types, + // violating an assertion results in program termination (i.e. SIGABRT). + _exit(1); + } + else if (g_pid_ds > 0) { + log_server::info("{}: Delay Server PID = [{}].", __func__, g_pid_ds); + } + else { + log_server::error("{}: Could not launch delay server [errno={}].", __func__, errno); + g_pid_ds = 0; + } + } + } // launch_delay_server + + auto migrate_and_launch_delay_server(bool _write_to_stdout, + bool _enable_test_mode, + bool& _first_boot, + std::chrono::steady_clock::time_point& _time_start) -> void + { + using namespace std::chrono_literals; + + // By remembering the initial value of _first_boot, we can keep the initial connection + // to the agent factory quiet. The reason for this is because the agent factory isn't guaranteed + // to be accepting connections by the time the first client connection is made from the main server + // process. + const auto did_first_boot = _first_boot; + + // TODO Replace 10s with the configuration value from server_config.json. + if (auto now = std::chrono::steady_clock::now(); _first_boot || (now - _time_start > 10s)) { + _first_boot = false; + _time_start = now; + + const auto hostname = boost::asio::ip::host_name(); + std::optional leader; + std::optional successor; + + try { + irods::experimental::client_connection conn; + leader = get_delay_server_leader(conn); + successor = get_delay_server_successor(conn); + + if (leader && successor) { + log_server::debug("{}: Delay server leader [{}] and successor [{}].", __func__, *leader, *successor); + + // 4 cases: + // L S + // ---- + // 0 0 (invalid state / no-op) + // 1 0 (migration complete) + // 0 1 (leader has shut down ds, successor launching ds) + // 1 1 (leader running ds, admin requested migration) + + // This server is the leader and may be running a delay server. + if (hostname == *leader) { + if (hostname == *successor) { + launch_delay_server(_write_to_stdout, _enable_test_mode); + + // Clear successor entry in catalog. This isn't necessary, but helps + // keep the admin from becoming confused. + if (g_pid_ds > 0) { + set_delay_server_migration_info(conn, KW_DELAY_SERVER_MIGRATION_IGNORE, ""); + } + } + else if (!successor->empty()) { + // Migration requested. Stop local delay server if running and clear the + // leader entry in the catalog. + if (g_pid_ds > 0) { + kill(g_pid_ds, SIGTERM); + + // Wait for delay server to complete current tasks. Depending on the workload, this + // could take minutes, hours, days to complete. + int status = 0; + waitpid(g_pid_ds, &status, 0); + log_server::info("Delay server has completed shutdown [exit_code={}].", WEXITSTATUS(status)); + g_pid_ds = 0; + } + + // TODO This isn't necessary if we can get task locking working (i.e. delay servers + // lock tasks just before execution). + // + // TODO However, we can uncomment the following if the server can confirm the host + // doesn't exist in the zone. + // + // Clear the leader entry. This acts as a signal to the successor server + // that it is safe to launch the delay server. + //set_delay_server_migration_info(conn, "", KW_DELAY_SERVER_MIGRATION_IGNORE); + } + else { + launch_delay_server(_write_to_stdout, _enable_test_mode); + } + } + else if (hostname == *successor) { + // leader == successor is covered by first if-branch. +#if 0 + if (leader->empty()) { + // The leader's delay server has been shut down. Launch the delay server if + // not running already. + + log_server::info("{}: Launching Delay Server.", __func__); + g_pid_ds = fork(); + if (0 == g_pid_ds) { + char pname[] = "/usr/sbin/irodsDelayServer"; // TODO This MUST NOT assume /usr/sbin. + char* args[] = {pname, nullptr}; // TODO Needs to take the config file path. + execv(pname, args); + _exit(1); + } + else if (g_pid_ds > 0) { + log_server::info("{}: Delay Server PID = [{}].", __func__, g_pid_ds); + set_delay_server_migration_info(conn, hostname, ""); + } + else { + log_server::error("{}: Could not launch delay server [errno={}].", __func__, errno); + } + } + else { + // TODO + // Determine when it's safe to auto-promote the successor to the leader + // (i.e. the leader value is never cleared). + + // 1. Connect to leader. + // 2. Use API to get the PID of the delay server. + // 3. If we find a PID for the delay server, try again later (because we're waiting for it to shutdown). + // 4. If we fail to reach the leader (due to network, etc), start counting failures. + // 5. If the successor fails to get the PID N times, auto-promote successor to leader. + } +#else + // Delay servers lock tasks before execution. This allows the successor server + // to launch a delay server without duplicating work. + launch_delay_server(_write_to_stdout, _enable_test_mode); + + if (g_pid_ds > 0) { + set_delay_server_migration_info(conn, hostname, ""); + } +#endif + } + else { + // TODO Reap child processes: agent factory, delay server + // TODO Fork agent factory and/or delay server again if necessary. + } + } + } + catch (const irods::exception& e) { + // It's possible the agent factory may not be ready for client requests. + // This situation is most visible during startup, when the delay server migration + // logic attempts to fetch the leader and successor hostnames from the catalog. + // + // If and when the connection from the main server process fails, log the error + // silently. We want startup (i.e. first boot) to clean. + if (did_first_boot && e.code() == USER_SOCK_CONNECT_ERR) { + log_server::trace("{}: {}", __func__, e.client_display_what()); + } + else { + log_server::error("{}: {}", __func__, e.client_display_what()); + } + } + catch (const std::exception& e) { + log_server::error("{}: {}", __func__, e.what()); + } + } + } // migrate_and_launch_delay_server + + auto log_stacktrace_files() -> void + { + for (auto&& entry : fs::directory_iterator{irods::get_irods_stacktrace_directory().c_str()}) { + // Expected filename format: + // + // .. + // + // 1. Extract the timestamp from the filename and convert it to ISO8601 format. + // 2. Extract the agent pid from the filename. + const auto p = entry.path().generic_string(); + + if (p.ends_with(irods::STACKTRACE_NOT_READY_FOR_LOGGING_SUFFIX)) { + log_server::trace("Skipping [{}] ...", p); + continue; + } + + auto slash_pos = p.rfind("/"); + + if (slash_pos == std::string::npos) { + log_server::trace("Skipping [{}]. No forward slash separator found.", p); + continue; + } + + ++slash_pos; + const auto first_dot_pos = p.find(".", slash_pos); + + if (first_dot_pos == std::string::npos) { + log_server::trace("Skipping [{}]. No dot separator found.", p); + continue; + } + + const auto last_dot_pos = p.rfind("."); + + if (last_dot_pos == std::string::npos || last_dot_pos == first_dot_pos) { + log_server::trace("Skipping [{}]. No dot separator found.", p); + continue; + } + + const auto epoch_seconds = p.substr(slash_pos, first_dot_pos - slash_pos); + const auto remaining_millis = p.substr(first_dot_pos + 1, last_dot_pos - (first_dot_pos + 1)); + const auto pid = p.substr(last_dot_pos + 1); + log_server::trace( + "epoch seconds = [{}], remaining millis = [{}], agent pid = [{}]", + epoch_seconds, + remaining_millis, + pid); + + try { + // Convert the epoch value to ISO8601 format. + log_server::trace("Converting epoch seconds to UTC timestamp."); + using boost::chrono::system_clock; + using boost::chrono::time_fmt; + const auto tp = system_clock::from_time_t(std::stoll(epoch_seconds)); + std::ostringstream utc_ss; + utc_ss << time_fmt(boost::chrono::timezone::utc, "%FT%T") << tp; + + // Read the contents of the file. + std::ifstream file{p}; + const auto stacktrace = boost::stacktrace::stacktrace::from_dump(file); + file.close(); + + // 3. Write the contents of the stacktrace file to syslog. + irods::experimental::log::server::critical({ + {"log_message", boost::stacktrace::to_string(stacktrace)}, + {"stacktrace_agent_pid", pid}, + {"stacktrace_timestamp_utc", fmt::format("{}.{}Z", utc_ss.str(), remaining_millis)}, + {"stacktrace_timestamp_epoch_seconds", epoch_seconds}, + {"stacktrace_timestamp_epoch_milliseconds", remaining_millis} + }); + + // 4. Delete the stacktrace file. + // + // We don't want the stacktrace files to go away without making it into the log. + // We can't rely on the log invocation above because of syslog. + // We don't want these files to accumulate for long running servers. + log_server::trace("Removing stacktrace file from disk."); + fs::remove(entry); + } + catch (...) { + // Something happened while logging the stacktrace file. + // Leaving the stacktrace file in-place for processing later. + log_server::trace("Caught exception while processing stacktrace file."); + } + } + } // log_stacktrace_files + + auto remove_leftover_agent_info_files_for_ips() -> void + { + for (const auto& entry : fs::directory_iterator{irods::get_irods_proc_directory().c_str()}) { + try { + const auto agent_pid = std::stoi(entry.path().stem().string()); + + // If the agent process does not exist or the main server process doesn't + // have permission to send signals to the agent process, then remove the + // agent file so that ips doesn't report it as an active agent. + if (kill(agent_pid, 0) == -1 && (ESRCH == errno || EPERM == errno)) { + fs::remove(entry); + } + } + catch (const std::exception& e) { + log_server::error("{}: {}: {}", __func__, entry.path().c_str(), e.what()); + } + }; + } // remove_leftover_agent_info_files_for_ips +} // anonymous namespace diff --git a/server/main_server/src/main_server_control_plane.cpp b/server/main_server/src/main_server_control_plane.cpp deleted file mode 100644 index f8ac91d78e..0000000000 --- a/server/main_server/src/main_server_control_plane.cpp +++ /dev/null @@ -1,1122 +0,0 @@ -#include "irods/irods_server_control_plane.hpp" - -#include "irods/genQuery.h" -#include "irods/irods_buffer_encryption.hpp" -#include "irods/irods_exception.hpp" -#include "irods/irods_logger.hpp" -#include "irods/irods_resource_manager.hpp" -#include "irods/irods_server_properties.hpp" -#include "irods/irods_server_state.hpp" -#include "irods/irods_stacktrace.hpp" -#include "irods/json_events.hpp" -#include "irods/miscServerFunct.hpp" -#include "irods/rcMisc.h" -#include "irods/rodsServer.hpp" -#include "irods/server_utilities.hpp" -#include "irods/sockComm.h" - -#include -#include -#include - -#include - -#include - -#include - -#include -#include - -#include -#include -#include -#include - -using log_server = irods::experimental::log::server; - -namespace irods -{ - static int ctrl_plane_signal_ = 0; - - static void ctrl_plane_handle_shutdown_signal(int sig) - { - ctrl_plane_signal_ = SIGTERM; - } - - static void ctrl_plane_sleep(int _s, int _ms) - { - useconds_t us = (_s * 1000000) + (_ms * 1000); - usleep(us); - } - - static void resolve_hostnames_using_server_config(std::vector& _hostnames) - { - std::transform(std::begin(_hostnames), std::end(_hostnames), std::begin(_hostnames), - [](std::string& _hostname) { - return resolve_hostname(_hostname, hostname_resolution_scheme::match_preferred) - .value_or(_hostname); - }); - } - - static error forward_server_control_command( - const std::string& _name, - const std::string& _host, - const std::string& _port_keyword, - std::string& _output) - { - if (EMPTY_RESC_HOST == _host) { - return SUCCESS(); - } - - int time_out, port, num_hash_rounds; - boost::optional encryption_algorithm; - buffer_crypt::array_t shared_secret; - try { - const auto config_handle{server_properties::instance().map()}; - const auto& config{config_handle.get_json()}; - time_out = config.at(KW_CFG_SERVER_CONTROL_PLANE_TIMEOUT).get(); - port = config.at(_port_keyword).get(); - num_hash_rounds = config.at(KW_CFG_SERVER_CONTROL_PLANE_ENCRYPTION_NUM_HASH_ROUNDS).get(); - encryption_algorithm.reset(config.at(KW_CFG_SERVER_CONTROL_PLANE_ENCRYPTION_ALGORITHM).get_ref()); - const auto& key = config.at(KW_CFG_SERVER_CONTROL_PLANE_KEY).get_ref(); - shared_secret.assign(key.begin(), key.end()); - } - catch (const nlohmann::json::exception& e) { - return ERROR(SYS_CONFIG_FILE_ERR, fmt::format("Control plane configuration error: {}", e.what())); - } - - // standard zmq rep-req communication pattern - zmq::context_t zmq_ctx( 1 ); - zmq::socket_t zmq_skt( zmq_ctx, ZMQ_REQ ); - zmq_skt.set(zmq::sockopt::rcvtimeo, time_out); - zmq_skt.set(zmq::sockopt::sndtimeo, time_out); - zmq_skt.set(zmq::sockopt::linger, 0); - - // this is the client so we connect rather than bind - const auto conn_str = fmt::format("tcp://{}:{}", _host, port); - - try { - zmq_skt.connect( conn_str.c_str() ); - } - catch (const zmq::error_t&) { - _output += "{\n \"failed_to_connect\" : \"" + conn_str + "\"\n},\n"; - return ERROR(SYS_INVALID_INPUT_PARAM, _output); - } - - - // build the command to forward - control_plane_command cmd; - cmd.command = _name; - cmd.options[ SERVER_CONTROL_OPTION_KW ] = SERVER_CONTROL_HOSTS_OPT; - cmd.options[ SERVER_CONTROL_HOST_KW ] = _host; - - // serialize using the generated avro class - auto out = avro::memoryOutputStream(); - avro::EncoderPtr e = avro::binaryEncoder(); - e->init( *out ); - avro::encode( *e, cmd ); - std::shared_ptr> data = avro::snapshot(*out); - - buffer_crypt crypt( - shared_secret.size(), // key size - 0, // salt size ( we dont send a salt ) - num_hash_rounds, // num hash rounds - encryption_algorithm->c_str() ); - - buffer_crypt::array_t iv; - buffer_crypt::array_t data_to_send; - buffer_crypt::array_t data_to_encrypt( - data->data(), - data->data() + data->size() ); - irods::error ret = crypt.encrypt( - shared_secret, - iv, - data_to_encrypt, - data_to_send ); - if ( !ret.ok() ) { - return PASS( ret ); - - } - - // copy binary encoding into a zmq message for transport - zmq::message_t rep( data_to_send.size() ); - std::memcpy( rep.data(), data_to_send.data(), data_to_send.size() ); - zmq_skt.send(rep, zmq::send_flags::none); - - // wait for the server response - zmq::message_t req; - { [[maybe_unused]] const auto ec = zmq_skt.recv(req); } - - if ( 0 == req.size() ) { - _output += "{\n \"response_message_is_empty_from\" : \"" + conn_str + "\"\n},\n"; - return ERROR( SYS_INVALID_INPUT_PARAM, "empty response string" ); - } - - // decrypt the message before passing to avro - buffer_crypt::array_t data_to_process; - const uint8_t* data_ptr = static_cast< const uint8_t* >( req.data() ); - buffer_crypt::array_t data_to_decrypt( - data_ptr, - data_ptr + req.size() ); - ret = crypt.decrypt( - shared_secret, - iv, - data_to_decrypt, - data_to_process ); - if ( !ret.ok() ) { - irods::log( PASS( ret ) ); - _output += "{\n \"failed_to_decrpyt_message_from\" : \"" + conn_str + "\"\n},\n"; - rodsLog( LOG_ERROR, "Failed to decrpyt [%s]", req.data() ); - return PASS( ret ); - } - - std::string rep_str( reinterpret_cast< char* >( data_to_process.data() ), data_to_process.size() ); - if ( SERVER_CONTROL_SUCCESS != rep_str ) { - // check if the result is really an error or a status - if ( std::string::npos == rep_str.find( "[-]" ) ) { - _output += rep_str; - } - else { - _output += "{\n \"invalid_message_format_from\" : \"" + conn_str + "\"\n},\n"; - return ERROR( CONTROL_PLANE_MESSAGE_ERROR, rep_str ); - } - } - - return SUCCESS(); - } // forward_server_control_command - - static void kill_delay_server() - { - if (const auto pid = irods::get_pid_from_file(irods::PID_FILENAME_DELAY_SERVER); pid) { - rodsLog(LOG_DEBUG, "[%s:%d] - sending kill signal to delay server", __FUNCTION__, __LINE__); - - // Previously, we ran kill_pid.py here. - // This would send three signals to the process, one right after the other: - // SIGSTOP (suspend), SIGTERM (terminate), then SIGKILL (kill) - // SIGSTOP is omitted here as it would prevent any signal handlers from running. - // SIGKILL does as well, but we currently lack handling for cases in which - // a SIGTERM signal handler fails to end the process. - - kill(*pid, SIGTERM); - kill(*pid, SIGKILL); - } - } // kill_delay_server - - static error server_operation_shutdown( - const std::string& _wait_option, - const size_t _wait_seconds, - std::string& _output) - { - rodsEnv my_env; - _reloadRodsEnv( my_env ); - _output += "{\n \"shutting down\": \""; - _output += my_env.rodsHost; - _output += "\"\n},\n"; - - int sleep_time_out_milli_sec = 0; - try { - sleep_time_out_milli_sec = get_server_property(KW_CFG_SERVER_CONTROL_PLANE_TIMEOUT); - } - catch (const irods::exception& e) { - return irods::error(e); - } - - if (SERVER_CONTROL_FORCE_AFTER_KW == _wait_option) { - // convert sec to millisec for comparison - sleep_time_out_milli_sec = _wait_seconds * 1000; - } - - int wait_milliseconds = SERVER_CONTROL_POLLING_TIME_MILLI_SEC; - - server_state::set_state(server_state::server_state::paused); - - int sleep_time = 0; - bool timeout_flg = false; - int proc_cnt = getAgentProcCnt(); - - kill_delay_server(); - - while (proc_cnt > 0 && !timeout_flg) { - // takes sec, millisec - ctrl_plane_sleep(0, wait_milliseconds); - - if (SERVER_CONTROL_WAIT_FOREVER_KW != _wait_option) { - sleep_time += SERVER_CONTROL_POLLING_TIME_MILLI_SEC; - if (sleep_time > sleep_time_out_milli_sec) { - timeout_flg = true; - } - } - - proc_cnt = getAgentProcCnt(); - } - - // actually shut down the server - server_state::set_state(server_state::server_state::stopped); - - // block until server exits to return - while (!timeout_flg) { - // takes sec, millisec - ctrl_plane_sleep(0, wait_milliseconds); - - sleep_time += SERVER_CONTROL_POLLING_TIME_MILLI_SEC; - if (sleep_time > sleep_time_out_milli_sec) { - timeout_flg = true; - } - - if (server_state::get_state() == server_state::server_state::exited) { - break; - } - } - - return SUCCESS(); - } // server_operation_shutdown - - static error rule_engine_operation_shutdown( - const std::string&, // _wait_option, - const size_t, // _wait_seconds, - std::string& _output) - { - rodsEnv my_env; - _reloadRodsEnv( my_env ); - _output += "{\n \"shutting down\": \""; - _output += my_env.rodsHost; - _output += "\"\n},\n"; - - server_state::set_state(server_state::server_state::stopped); - - return SUCCESS(); - } // rule_engine_server_operation_shutdown - - static error operation_pause( - const std::string&, // _wait_option, - const size_t, // _wait_seconds, - std::string& _output) - { - rodsEnv my_env; - _reloadRodsEnv( my_env ); - _output += "{\n \"pausing\": \""; - _output += my_env.rodsHost; - _output += "\"\n},\n"; - - server_state::set_state(server_state::server_state::paused); - - return SUCCESS(); - } // operation_pause - - static error operation_resume( - const std::string&, // _wait_option, - const size_t, // _wait_seconds, - std::string& _output) - { - rodsEnv my_env; - _reloadRodsEnv( my_env ); - _output += "{\n \"resuming\": \""; - _output += my_env.rodsHost; - _output += "\"\n},\n"; - - server_state::set_state(server_state::server_state::running); - - return SUCCESS(); - } // operation_resume - - static int get_pid_age(pid_t _pid) - { - std::vector args{std::to_string(_pid)}; - - std::string pid_age; - irods::error ret = get_script_output_single_line("python3", "pid_age.py", args, pid_age); - if (!ret.ok()) { - irods::log(PASS(ret)); - return 0; - } - - double age = 0.0; - try { - age = boost::lexical_cast(pid_age); - } - catch (const boost::bad_lexical_cast&) { - rodsLog(LOG_ERROR, "get_pid_age bad lexical cast for [%s]", pid_age.c_str()); - } - - return static_cast(age); - } // get_pid_age - - static error operation_status( - const std::string&, // _wait_option, - const size_t, // _wait_seconds, - std::string& _output) - { - rodsEnv my_env; - _reloadRodsEnv( my_env ); - - using json = nlohmann::json; - - json obj{ - {"hostname", my_env.rodsHost}, - {"irods_server_pid", getpid()}, - {"delay_server_pid", irods::get_pid_from_file(irods::PID_FILENAME_DELAY_SERVER).value_or(0)} - }; - - obj["status"] = to_string(server_state::get_state()); - - auto arr = json::array(); - - std::vector pids; - getAgentProcPIDs( pids ); - for ( size_t i = 0; i < pids.size(); ++i ) { - int pid = pids[i]; - int age = get_pid_age( pids[i] ); - - arr.push_back(json::object({ - {"agent_pid", pid}, - {"age", age} - })); - } - - obj["agents"] = arr; - - _output += obj.dump(4); - _output += ","; - - return SUCCESS(); - } // operation_status - - static error operation_ping( - const std::string&, // _wait_option, - const size_t, // _wait_seconds, - std::string& _output) - { - _output += "{\n \"status\": \"alive\"\n},\n"; - /*rodsEnv my_env; - _reloadRodsEnv( my_env ); - _output += "{\n \"pinging\": \""; - _output += my_env.rodsHost; - _output += "\"\n},\n";*/ - return SUCCESS(); - } - - static auto operation_reload(const std::string& _wait_option, const size_t _wait_seconds, std::string& _output) - -> error - { - using json = nlohmann::json; - - rodsEnv my_env; - _reloadRodsEnv(my_env); - - auto diff = irods::server_properties::instance().reload(); - irods::experimental::json_events::run_hooks(diff); - - json obj = {{"configuration_changes_made", diff}, {"hostname", my_env.rodsHost}}; - _output += obj.dump(4); - _output += ","; - - log_server::info("Control plane received signal to reload configuration"); - - return SUCCESS(); - } // operation_reload - - bool server_control_executor::is_host_in_list(const std::string& _hn, const host_list_t& _hosts) - { - const auto e = std::end(_hosts); - - return e != std::find_if(std::begin(_hosts), e, [&_hn](const std::string& _hostname) { - return _hostname == _hn; - }); - } // is_host_in_list - - server_control_plane::server_control_plane(const std::string& _prop, - std::atomic& _is_accepting_requests) - : control_executor_(_prop, _is_accepting_requests) - , control_thread_(boost::ref(control_executor_)) - { - } // ctor - - server_control_plane::~server_control_plane() - { - try { - control_thread_.join(); - } - catch ( const boost::thread_resource_error& ) { - rodsLog(LOG_ERROR, "boost encountered thread_resource_error on join in server_control_plane destructor."); - } - } // dtor - - server_control_executor::server_control_executor(const std::string& _prop, - std::atomic& _is_accepting_requests) - : port_prop_(_prop) - , op_map_{} - , local_server_hostname_{} - , provider_hostname_{} - , is_accepting_requests_{_is_accepting_requests} - { - if (port_prop_.empty()) { - THROW(SYS_INVALID_INPUT_PARAM, "control_plane_port key is empty"); - } - - is_accepting_requests_.store(false); - - op_map_[SERVER_CONTROL_PAUSE] = operation_pause; - op_map_[SERVER_CONTROL_RESUME] = operation_resume; - op_map_[SERVER_CONTROL_STATUS] = operation_status; - op_map_[SERVER_CONTROL_PING] = operation_ping; - op_map_[SERVER_CONTROL_RELOAD] = operation_reload; - if (_prop == KW_CFG_RULE_ENGINE_CONTROL_PLANE_PORT) { - op_map_[SERVER_CONTROL_SHUTDOWN] = rule_engine_operation_shutdown; - } - else { - op_map_[SERVER_CONTROL_SHUTDOWN] = server_operation_shutdown; - } - - // get our hostname for ordering - rodsEnv my_env; - _reloadRodsEnv( my_env ); - local_server_hostname_ = my_env.rodsHost; - - // get the provider's hostname host for ordering - const auto config_handle{server_properties::instance().map()}; - const auto& config{config_handle.get_json()}; - provider_hostname_ = config.at(KW_CFG_CATALOG_PROVIDER_HOSTS)[0].get_ref(); - - // repave provider_hostname_ as we do not want to process 'localhost' - if ("localhost" == provider_hostname_) { - provider_hostname_ = local_server_hostname_; - rodsLog(LOG_ERROR, "server_control_executor - provider's hostname is localhost, not a fqdn"); - // TODO :: throw fancy exception here when we disallow localhost - } - } // ctor - - error server_control_executor::forward_command( - const std::string& _name, - const std::string& _host, - const std::string& _port_keyword, - const std::string& _wait_option, - const size_t& _wait_seconds, - std::string& _output) - { - // if this is forwarded to us, just perform the operation - if (_host == local_server_hostname_) { - return process_host_list(_name, _wait_option, _wait_seconds, {_host}, _output); - } - - return forward_server_control_command(_name, _host, _port_keyword, _output); - } // forward_command - - error server_control_executor::get_resource_host_names(host_list_t& _host_names) - { - rodsEnv my_env; - _reloadRodsEnv( my_env ); - rcComm_t* comm = rcConnect( - my_env.rodsHost, - my_env.rodsPort, - my_env.rodsUserName, - my_env.rodsZone, - NO_RECONN, 0 ); - if ( !comm ) { - return ERROR(NULL_VALUE_ERR, "rcConnect failed"); - } - - int status = clientLogin(comm, 0, my_env.rodsAuthScheme); - if ( status != 0 ) { - rcDisconnect( comm ); - return ERROR(status, "client login failed"); - } - - genQueryInp_t gen_inp; - genQueryOut_t* gen_out = NULL; - memset( &gen_inp, 0, sizeof( gen_inp ) ); - - addInxIval( &gen_inp.selectInp, COL_R_LOC, 1 ); - gen_inp.maxRows = MAX_SQL_ROWS; - - int cont_idx = 1; - while ( cont_idx ) { - int status = rcGenQuery(comm, &gen_inp, &gen_out); - if ( status < 0 ) { - rcDisconnect( comm ); - freeGenQueryOut( &gen_out ); - clearGenQueryInp( &gen_inp ); - return ERROR(status, "genQuery failed."); - - } // if - - sqlResult_t* resc_loc = getSqlResultByInx(gen_out, COL_R_LOC); - if ( !resc_loc ) { - rcDisconnect( comm ); - freeGenQueryOut( &gen_out ); - clearGenQueryInp( &gen_inp ); - return ERROR(UNMATCHED_KEY_OR_INDEX, "getSqlResultByInx for COL_R_LOC failed"); - } - - for ( int i = 0; i < gen_out->rowCnt; ++i ) { - const std::string hn(&resc_loc->value[resc_loc->len * i]); - if ("localhost" != hn) { - _host_names.push_back(hn); - } - } - - cont_idx = gen_out->continueInx; - } - - freeGenQueryOut( &gen_out ); - clearGenQueryInp( &gen_inp ); - - status = rcDisconnect( comm ); - if ( status < 0 ) { - return ERROR(status, "failed in rcDisconnect"); - } - - return SUCCESS(); - } // get_resource_host_names - - void server_control_executor::operator()() - { - signal(SIGINT, ctrl_plane_handle_shutdown_signal); - signal(SIGTERM, ctrl_plane_handle_shutdown_signal); - - int port, num_hash_rounds; - boost::optional encryption_algorithm; - buffer_crypt::array_t shared_secret; - try { - const auto config_handle{server_properties::instance().map()}; - const auto& config{config_handle.get_json()}; - port = config.at(port_prop_).get(); - num_hash_rounds = config.at(KW_CFG_SERVER_CONTROL_PLANE_ENCRYPTION_NUM_HASH_ROUNDS).get(); - encryption_algorithm.reset(config.at(KW_CFG_SERVER_CONTROL_PLANE_ENCRYPTION_ALGORITHM).get_ref()); - const auto& key = config.at(KW_CFG_SERVER_CONTROL_PLANE_KEY).get_ref(); - shared_secret.assign(key.begin(), key.end()); - } - catch (const nlohmann::json::exception& e) { - log_server::error("Control plane configuration error: {}", e.what()); - return; - } - - if (shared_secret.empty() || - encryption_algorithm->empty() || - 0 == port || - 0 == num_hash_rounds) - { - rodsLog(LOG_NOTICE, "control plane is not configured properly"); - return; - } - - while ( true ) { - try { - zmq::context_t zmq_ctx( 1 ); - zmq::socket_t zmq_skt( zmq_ctx, ZMQ_REP ); - - int time_out = SERVER_CONTROL_POLLING_TIME_MILLI_SEC; - zmq_skt.set(zmq::sockopt::rcvtimeo, time_out); - zmq_skt.set(zmq::sockopt::sndtimeo, time_out); - zmq_skt.set(zmq::sockopt::linger, 0); - - const auto conn_str = fmt::format("tcp://*:{}", port); - zmq_skt.bind( conn_str.c_str() ); - - rodsLog(LOG_NOTICE, ">>> control plane :: listening on port %d\n", port); - - // Let external components know that the control plane is ready - // to accept requests. - is_accepting_requests_.store(true); - - while (true) { - const auto state = server_state::get_state(); - - if (state == server_state::server_state::stopped || - state == server_state::server_state::exited) - { - break; - } - - std::string output; - - switch (ctrl_plane_signal_ ) { - case 0: { - // Wait for a request. - zmq::message_t req; - { [[maybe_unused]] const auto ec = zmq_skt.recv(req); } - if ( 0 == req.size() ) { - continue; - } - - // process the message - std::string rep_msg( SERVER_CONTROL_SUCCESS ); - error ret = process_operation( req, output ); - - rep_msg = output; - if ( !ret.ok() ) { - log( PASS( ret ) ); - } - - if ( !output.empty() ) { - rep_msg = output; - } - - buffer_crypt crypt( - shared_secret.size(), // key size - 0, // salt size ( we dont send a salt ) - num_hash_rounds, // num hash rounds - encryption_algorithm->c_str() ); - - buffer_crypt::array_t iv; - buffer_crypt::array_t data_to_send; - buffer_crypt::array_t data_to_encrypt( - rep_msg.begin(), - rep_msg.end() ); - ret = crypt.encrypt( - shared_secret, - iv, - data_to_encrypt, - data_to_send ); - if ( !ret.ok() ) { - irods::log( PASS( ret ) ); - } - - zmq::message_t rep( data_to_send.size() ); - std::memcpy(rep.data(), data_to_send.data(), data_to_send.size()); - - zmq_skt.send(rep, zmq::send_flags::none); - - break; - } - case SIGINT: - case SIGTERM: - case SIGHUP: { -#ifdef __GLIBC__ -#if __GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 32) - rodsLog(LOG_NOTICE, - ">>> control plane :: received signal SIG%s, performing shutdown operation\n", - sigabbrev_np(ctrl_plane_signal_)); -#else - rodsLog(LOG_NOTICE, - ">>> control plane :: received signal %s, performing shutdown operation\n", - sys_siglist[ctrl_plane_signal_]); -#endif -#else - rodsLog(LOG_NOTICE, - ">>> control plane :: received signal (%s), performing shutdown operation\n", - strsignal(ctrl_plane_signal_)); -#endif - host_list_t cmd_hosts; - cmd_hosts.push_back(local_server_hostname_); - error ret = perform_operation( - SERVER_CONTROL_SHUTDOWN, - SERVER_CONTROL_HOSTS_OPT, - "", - 0, - cmd_hosts, - output ); - if ( !ret.ok() ) { - log( PASS( ret ) ); - } - - break; - } - } - } - - // exited control loop normally, we're done - break; - } - catch (const zmq::error_t& e) { - rodsLog(LOG_ERROR, "ZMQ encountered an error in the control plane loop: [%s] Restarting control thread...", e.what()); - } - catch (const std::exception& e) { - rodsLog(LOG_ERROR, "Encountered an error in the control plane loop: [%s] Restarting control thread...", e.what()); - } - - is_accepting_requests_.store(false); - } - } // control operation - - error server_control_executor::notify_provider_and_local_servers_preop( - const std::string& _cmd_name, - const std::string& _cmd_option, - const std::string& _wait_option, - const size_t& _wait_seconds, - const host_list_t& _cmd_hosts, - std::string& _output) - { - if (SERVER_CONTROL_RESUME != _cmd_name) { - return SUCCESS(); - } - - error ret = SUCCESS(); - const bool is_all_opt = (SERVER_CONTROL_ALL_OPT == _cmd_option); - const bool found_my_host = is_host_in_list(local_server_hostname_, _cmd_hosts); - const bool found_provider_host = is_host_in_list(provider_hostname_, _cmd_hosts); - const bool is_provider_host = (local_server_hostname_ == provider_hostname_); - - // for pause or shutdown: pre-op forwards to the provider first, - // then to itself, then others - // for resume: we skip doing work here (we'll go last in post-op) - if (found_provider_host || is_all_opt) { - ret = forward_command( - _cmd_name, - provider_hostname_, - KW_CFG_SERVER_CONTROL_PLANE_PORT, - _wait_option, - _wait_seconds, - _output); - // takes sec, microsec - ctrl_plane_sleep(0, SERVER_CONTROL_FWD_SLEEP_TIME_MILLI_SEC); - } - - // pre-op forwards to the local server second - // such as for resume - if (!is_provider_host && (found_my_host || is_all_opt)) { - ret = forward_command( - _cmd_name, - local_server_hostname_, - KW_CFG_SERVER_CONTROL_PLANE_PORT, - _wait_option, - _wait_seconds, - _output ); - } - - return ret; - } // notify_provider_and_local_servers_preop - - error server_control_executor::notify_provider_and_local_servers_postop( - const std::string& _cmd_name, - const std::string& _cmd_option, - const std::string& _wait_option, - const size_t& _wait_seconds, - const host_list_t& _cmd_hosts, - std::string& _output ) - { - error ret = SUCCESS(); - if ( SERVER_CONTROL_RESUME == _cmd_name ) { - return SUCCESS(); - } - - bool is_all_opt = (SERVER_CONTROL_ALL_OPT == _cmd_option); - const bool found_my_host = is_host_in_list( local_server_hostname_, _cmd_hosts ); - const bool found_provider_host = is_host_in_list( provider_hostname_, _cmd_hosts ); - const bool is_provider_host = (local_server_hostname_ == provider_hostname_ ); - - // post-op forwards to the local server first - // then the provider such as for shutdown - if (!is_provider_host && (found_my_host || is_all_opt)) { - ret = forward_command( - _cmd_name, - local_server_hostname_, - KW_CFG_SERVER_CONTROL_PLANE_PORT, - _wait_option, - _wait_seconds, - _output ); - } - - // post-op forwards to the provider last - if (found_provider_host || is_all_opt) { - ret = forward_command( - _cmd_name, - provider_hostname_, - KW_CFG_SERVER_CONTROL_PLANE_PORT, - _wait_option, - _wait_seconds, - _output ); - } - - return ret; - } // notify_provider_and_local_servers_postop - - error server_control_executor::validate_host_list( - const host_list_t& _irods_hosts, - const host_list_t& _target_hosts, - host_list_t& _valid_hosts) - { - for (auto&& target_host : _target_hosts) { - // Return an error if the hostname does not match the hostname of a consumer or the provider. - if (!is_host_in_list(target_host, _irods_hosts) && target_host != provider_hostname_) { - return ERROR(SYS_INVALID_INPUT_PARAM, fmt::format("invalid server hostname [{}]", target_host)); - } - - // Skip the local server and provider because these are handled by server_operation_shutdown() directly. - if (provider_hostname_ == target_host || local_server_hostname_ == target_host) { - continue; - } - - // Add the host to our newly ordered list. - _valid_hosts.push_back(target_host); - } - - return SUCCESS(); - } // validate_host_list - - error server_control_executor::extract_command_parameters( - const control_plane_command& _cmd, - std::string& _name, - std::string& _option, - std::string& _wait_option, - size_t& _wait_seconds, - host_list_t& _hosts ) - { - // capture and validate the command parameter - _name = _cmd.command; - if (op_map_.count(_name) == 0) { - return ERROR(SYS_INVALID_INPUT_PARAM, fmt::format("invalid command [{}]", _name)); - } - - // capture and validate the option parameter - auto itr = _cmd.options.find(SERVER_CONTROL_OPTION_KW); - if (_cmd.options.end() == itr) { - return ERROR(SYS_INVALID_INPUT_PARAM, "option parameter is empty"); - } - - _option = itr->second; - if (SERVER_CONTROL_ALL_OPT != _option && SERVER_CONTROL_HOSTS_OPT != _option) { - return ERROR(SYS_INVALID_INPUT_PARAM, fmt::format("invalid command option [{}]", _option)); - } - - // capture and validate the server hosts, skip the option key - for (auto&& [k, v] : _cmd.options) { - if (k == SERVER_CONTROL_OPTION_KW) { - continue; - } - else if (k == SERVER_CONTROL_FORCE_AFTER_KW) { - _wait_option = SERVER_CONTROL_FORCE_AFTER_KW; - _wait_seconds = boost::lexical_cast(v); - } - else if (k == SERVER_CONTROL_WAIT_FOREVER_KW) { - _wait_option = SERVER_CONTROL_WAIT_FOREVER_KW; - _wait_seconds = 0; - } - else if (k.find(SERVER_CONTROL_HOST_KW) != std::string::npos) { - _hosts.push_back(v); - } - else { - return ERROR(SYS_INVALID_INPUT_PARAM, fmt::format("invalid option key [{}]", k)); - } - } - - return SUCCESS(); - } // extract_command_parameters - - error server_control_executor::process_host_list( - const std::string& _cmd_name, - const std::string& _wait_option, - const size_t& _wait_seconds, - const host_list_t& _hosts, - std::string& _output) - { - if (_hosts.empty()) { - return SUCCESS(); - } - - error fwd_err = SUCCESS(); - - for (auto&& host : _hosts) { - if ("localhost" == host) { - continue; - } - - std::string output; - - // We are the target server. - if (host == local_server_hostname_) { - error ret = op_map_[_cmd_name](_wait_option, _wait_seconds, output); - - if (!ret.ok()) { - fwd_err = PASS(ret); - } - - _output += output; - } - // Forward to the correct server. - else { - error ret = forward_command(_cmd_name, host, port_prop_, _wait_option, _wait_seconds, output); - - if (!ret.ok()) { - _output += output; - log(PASS(ret)); - fwd_err = PASS(ret); - } - else { - _output += output; - } - } - } - - return fwd_err; - } // process_host_list - - error server_control_executor::process_operation(const zmq::message_t& _msg, std::string& _output) - { - control_plane_command cmd; - - irods::error ret = decrypt_incoming_command(_msg, cmd); - if ( !ret.ok() ) { - irods::log( PASS( ret ) ); - return PASS( ret ); - } - - std::string cmd_name, cmd_option, wait_option; - host_list_t cmd_hosts; - size_t wait_seconds = 0; - ret = extract_command_parameters( - cmd, - cmd_name, - cmd_option, - wait_option, - wait_seconds, - cmd_hosts ); - if ( !ret.ok() ) { - irods::log( PASS( ret ) ); - return PASS( ret ); - } - - ret = perform_operation( - cmd_name, - cmd_option, - wait_option, - wait_seconds, - cmd_hosts, - _output ); - if ( !ret.ok() ) { - irods::log( PASS( ret ) ); - return PASS( ret ); - } - - return SUCCESS(); - } // process_operation - - error server_control_executor::decrypt_incoming_command( - const zmq::message_t& _msg, - irods::control_plane_command& _cmd ) { - if ( _msg.size() <= 0 ) { - return SUCCESS(); - } - - int num_hash_rounds; - boost::optional encryption_algorithm; - buffer_crypt::array_t shared_secret; - try { - const auto config_handle{server_properties::instance().map()}; - const auto& config{config_handle.get_json()}; - num_hash_rounds = config.at(KW_CFG_SERVER_CONTROL_PLANE_ENCRYPTION_NUM_HASH_ROUNDS).get(); - encryption_algorithm.reset(config.at(KW_CFG_SERVER_CONTROL_PLANE_ENCRYPTION_ALGORITHM).get_ref()); - const auto& key = config.at(KW_CFG_SERVER_CONTROL_PLANE_KEY).get_ref(); - shared_secret.assign(key.begin(), key.end()); - } - catch (const nlohmann::json::exception& e) { - return ERROR(SYS_CONFIG_FILE_ERR, fmt::format("Control plane configuration error: {}", e.what())); - } - - // decrypt the message before passing to avro - buffer_crypt crypt( - shared_secret.size(), // key size - 0, // salt size ( we dont send a salt ) - num_hash_rounds, // num hash rounds - encryption_algorithm->c_str()); - - buffer_crypt::array_t iv; - buffer_crypt::array_t data_to_process; - - const uint8_t* data_ptr = static_cast(_msg.data()); - buffer_crypt::array_t data_to_decrypt( - data_ptr, - data_ptr + _msg.size()); - irods::error ret = crypt.decrypt( - shared_secret, - iv, - data_to_decrypt, - data_to_process); - if ( !ret.ok() ) { - irods::log( PASS( ret ) ); - return PASS( ret ); - } - - auto in = avro::memoryInputStream( - static_cast(data_to_process.data()), - data_to_process.size()); - avro::DecoderPtr dec = avro::binaryDecoder(); - dec->init( *in ); - - avro::decode( *dec, _cmd ); - - return SUCCESS(); - } // decrypt_incoming_command - - error server_control_executor::perform_operation( - const std::string& _cmd_name, - const std::string& _cmd_option, - const std::string& _wait_option, - const size_t& _wait_seconds, - const host_list_t& _cmd_hosts, - std::string& _output ) { - error final_ret = SUCCESS(); - host_list_t cmd_hosts = _cmd_hosts; - - // add safeguards - if server is paused only allow a resume call - if (server_state::get_state() == server_state::server_state::paused && - SERVER_CONTROL_RESUME != _cmd_name) - { - _output = SERVER_PAUSED_ERROR; - return SUCCESS(); - } - - resolve_hostnames_using_server_config(cmd_hosts); - - // the provider needs to be notified first in certain - // cases such as RESUME where it is needed to capture - // the host list for validation, etc - irods::error ret = notify_provider_and_local_servers_preop( - _cmd_name, - _cmd_option, - _wait_option, - _wait_seconds, - cmd_hosts, - _output ); - if ( !ret.ok() ) { - final_ret = PASS( ret ); - irods::log( final_ret ); - } - - host_list_t irods_hosts; - ret = get_resource_host_names( irods_hosts ); - if ( !ret.ok() ) { - final_ret = PASS( ret ); - irods::log( final_ret ); - } - - if ( SERVER_CONTROL_ALL_OPT == _cmd_option ) { - cmd_hosts = irods_hosts; - } - - host_list_t valid_hosts; - ret = validate_host_list( irods_hosts, cmd_hosts, valid_hosts ); - if ( !ret.ok() ) { - final_ret = PASS( ret ); - irods::log( final_ret ); - } - - ret = process_host_list( - _cmd_name, - _wait_option, - _wait_seconds, - valid_hosts, - _output ); - if ( !ret.ok() ) { - final_ret = PASS( ret ); - irods::log( final_ret ); - } - - // the provider needs to be notified last in certain - // cases such as SHUTDOWN or PAUSE where it is - // needed to capture the host list for validation - ret = notify_provider_and_local_servers_postop( - _cmd_name, - _cmd_option, - _wait_option, - _wait_seconds, - cmd_hosts, // dont want sanitized - _output ); - if ( !ret.ok() ) { - final_ret = PASS( ret ); - irods::log( final_ret ); - } - - return final_ret; - } // process_operation -} // namespace irods diff --git a/server/main_server/src/rodsServer.cpp b/server/main_server/src/rodsServer.cpp deleted file mode 100644 index 8e1ca7dbad..0000000000 --- a/server/main_server/src/rodsServer.cpp +++ /dev/null @@ -1,2568 +0,0 @@ -#include "irods/rodsServer.hpp" - -#include "irods/client_api_allowlist.hpp" -#include "irods/client_connection.hpp" -#include "irods/irods_at_scope_exit.hpp" -#include "irods/irods_buffer_encryption.hpp" -#include "irods/irods_configuration_keywords.hpp" -#include "irods/irods_configuration_parser.hpp" -#include "irods/irods_get_full_path_for_config_file.hpp" -#include "irods/json_events.hpp" -#include "irods/sharedmemory.hpp" -#include "irods/initServer.hpp" -#include "irods/miscServerFunct.hpp" -#include "irods/irods_exception.hpp" -#include "irods/irods_server_state.hpp" -#include "irods/irods_client_server_negotiation.hpp" -#include "irods/irods_network_factory.hpp" -#include "irods/irods_re_plugin.hpp" -#include "irods/irods_server_properties.hpp" -#include "irods/irods_server_control_plane.hpp" -#include "irods/rsGlobalExtern.hpp" -#include "irods/locks.hpp" -#include "irods/sockCommNetworkInterface.hpp" -#include "irods/irods_random.hpp" -#include "irods/replica_access_table.hpp" -#include "irods/irods_logger.hpp" -#include "irods/hostname_cache.hpp" -#include "irods/dns_cache.hpp" -#include "irods/server_utilities.hpp" -#include "irods/process_manager.hpp" -#include "irods/irods_default_paths.hpp" -#include "irods/irods_signal.hpp" -#include "irods/irods_server_api_table.hpp" -#include "irods/irods_client_api_table.hpp" -#include "irods/icatHighLevelRoutines.hpp" -#include "irods/rodsAgent.hpp" -#include "irods/physPath.hpp" - -#include "irods/rcMisc.h" -#include "irods/rodsErrorTable.h" -#include "irods/getRodsEnv.h" -#include "irods/procLog.h" -#include "irods/plugins/api/grid_configuration_types.h" -#include "irods/get_grid_configuration_value.h" -#include "irods/set_delay_server_migration_info.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -// __has_feature is a Clang specific feature. -// The preprocessor code below exists so that other compilers can be used (e.g. GCC). -#ifndef __has_feature -# define __has_feature(feature) 0 -#endif - -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) -# include - -// Defines default options for running iRODS with Address Sanitizer enabled. -// This is a convenience function which allows the iRODS server to start without -// having to specify options via environment variables. -extern "C" const char* __asan_default_options() -{ - // See root CMakeLists.txt file for definition. - return IRODS_ADDRESS_SANITIZER_DEFAULT_OPTIONS; -} // __asan_default_options -#endif - -#define LOCK_FILE_PURGE_TIME 7200 // Purge lock files every 2 hr. - -// clang-format off -namespace ix = irods::experimental; -// clang-format on - -using log_server = irods::experimental::log::server; - -struct sockaddr_un local_addr{}; -int agent_conn_socket{}; -bool connected_to_agent{}; - -pid_t agent_spawning_pid{}; -char unix_domain_socket_directory[] = "/tmp/irods_sockets_XXXXXX"; -char agent_factory_socket_file[sizeof(sockaddr_un::sun_path)]{}; - -unsigned int ServerBootTime; -int SvrSock; - -std::atomic is_control_plane_accepting_requests = false; -int failed_delay_server_migration_communication_attempts = 0; - -std::atomic reload_server_config = false; - -agentProc_t* ConnectedAgentHead{}; -agentProc_t* ConnReqHead{}; -agentProc_t* SpawnReqHead{}; -agentProc_t* BadReqHead{}; - -boost::mutex ConnectedAgentMutex; -boost::mutex BadReqMutex; -boost::thread* ReadWorkerThread[NUM_READ_WORKER_THR]; -boost::thread* SpawnManagerThread; - -boost::thread* PurgeLockFileThread; - -boost::mutex ReadReqCondMutex; -boost::mutex SpawnReqCondMutex; -boost::condition_variable ReadReqCond; -boost::condition_variable SpawnReqCond; - -namespace -{ - // We incorporate the cache salt into the rule engine's named_mutex and shared memory object. - // This prevents (most of the time) an orphaned mutex from halting server standup. Issue most often seen - // when a running iRODS installation is uncleanly killed (leaving the file system object used to implement - // boost::named_mutex e.g. in /var/run/shm) and then the iRODS user account is recreated, yielding a different - // UID. The new iRODS user account is then unable to unlock or remove the existing mutex, blocking the server. - irods::error createAndSetRECacheSalt() - { - // Should only ever set the cache salt once. - try { - const auto& existing_salt = irods::get_server_property(irods::KW_CFG_RE_CACHE_SALT); - log_server::debug("createAndSetRECacheSalt: Cache salt already set [{}]", existing_salt.c_str()); - return ERROR(SYS_ALREADY_INITIALIZED, "createAndSetRECacheSalt: Cache salt already set"); - } - catch (const irods::exception&) { - irods::buffer_crypt::array_t buf; - irods::error ret = irods::buffer_crypt::generate_key(buf, RE_CACHE_SALT_NUM_RANDOM_BYTES); - if (!ret.ok()) { - log_server::critical("createAndSetRECacheSalt: failed to generate random bytes"); - return PASS(ret); - } - - std::string cache_salt_random; - ret = irods::buffer_crypt::hex_encode(buf, cache_salt_random); - if (!ret.ok()) { - log_server::critical("createAndSetRECacheSalt: failed to hex encode random bytes"); - return PASS(ret); - } - - const auto cache_salt = fmt::format("pid{}_{}", static_cast(getpid()), cache_salt_random); - - try { - irods::set_server_property(irods::KW_CFG_RE_CACHE_SALT, cache_salt); - } - catch (const nlohmann::json::exception& e) { - log_server::critical("createAndSetRECacheSalt: failed to set server_properties"); - return ERROR(SYS_INVALID_INPUT_PARAM, e.what()); - } - catch (const std::exception&) {} - - if (setenv(SP_RE_CACHE_SALT, cache_salt.c_str(), 1) != 0) { - log_server::critical("createAndSetRECacheSalt: failed to set environment variable"); - return ERROR(SYS_SETENV_ERR, "createAndSetRECacheSalt: failed to set environment variable"); - } - - return SUCCESS(); - } - } - - void get64RandomBytes(char* buf) - { - const int num_random_bytes = 32; - unsigned char random_bytes[num_random_bytes]; - irods::getRandomBytes(random_bytes, sizeof(random_bytes)); - - std::stringstream ss; - for (std::size_t i = 0; i < sizeof(random_bytes); ++i) { - ss << std::hex << std::setw(2) << std::setfill('0') << (unsigned int) random_bytes[i]; - } - - const int num_hex_bytes = 2 * num_random_bytes; - std::snprintf(buf, num_hex_bytes + 1, "%s", ss.str().c_str()); - } // get64RandomBytes - - bool init_shared_memory_for_plugin(const nlohmann::json& _plugin_object) - { - const auto itr = _plugin_object.find(irods::KW_CFG_SHARED_MEMORY_INSTANCE); - - if (_plugin_object.end() != itr) { - const auto& mem_name = itr->get_ref(); - prepareServerSharedMemory(mem_name); - return true; - } - - return false; - } // init_shared_memory_for_plugin - - irods::error init_shared_memory_for_plugins() - { - try { - const auto config_handle{irods::server_properties::instance().map()}; - const auto& config{config_handle.get_json()}; - - for (const auto& item : config.at(irods::KW_CFG_PLUGIN_CONFIGURATION).items()) { - for (const auto& plugin : item.value().items()) { - init_shared_memory_for_plugin(plugin.value()); - } - } - } - catch (const irods::exception& e) { - return irods::error(e); - } - catch (const std::exception& e) { - return ERROR(SYS_INTERNAL_ERR, e.what()); - } - - return SUCCESS(); - } // init_shared_memory_for_plugins - - bool deinit_shared_memory_for_plugin(const nlohmann::json& _plugin_object) - { - const auto itr = _plugin_object.find(irods::KW_CFG_SHARED_MEMORY_INSTANCE); - - if (_plugin_object.end() != itr) { - const auto& mem_name = itr->get_ref(); - removeSharedMemory(mem_name); - resetMutex(mem_name.c_str()); - return true; - } - - return false; - } // deinit_shared_memory_for_plugin - - irods::error deinit_shared_memory_for_plugins() - { - try { - const auto config_handle{irods::server_properties::instance().map()}; - const auto& config{config_handle.get_json()}; - - for (const auto& item : config.at(irods::KW_CFG_PLUGIN_CONFIGURATION).items()) { - for (const auto& plugin : item.value().items()) { - deinit_shared_memory_for_plugin(plugin.value()); - } - } - } - catch (const irods::exception& e) { - return irods::error(e); - } - catch (const std::exception& e) { - return ERROR(SYS_INTERNAL_ERR, e.what()); - } - - return SUCCESS(); - } // deinit_shared_memory_for_plugins - - std::optional load_server_config() - { - try { - // Find the server configuration file. - std::string config_path; - - if (const auto err = irods::get_full_path_for_config_file("server_config.json", config_path); !err.ok()) { - return std::nullopt; - } - - // Load the server configuration file in as JSON. - nlohmann::json config; - - if (std::ifstream in{config_path}; in) { - in >> config; - } - else { - return std::nullopt; - } - - return config; - } - catch (...) { - } - - return std::nullopt; - } // load_server_config - - void - init_logger(const nlohmann::json& _server_config, bool _write_to_stdout = false, bool _enable_test_mode = false) - { - ix::log::init(_write_to_stdout, _enable_test_mode); - irods::server_properties::instance().capture(); - log_server::set_level(ix::log::get_level_from_config(irods::KW_CFG_LOG_LEVEL_CATEGORY_SERVER)); - ix::log::set_server_type("server"); - ix::log::set_server_zone(_server_config.at(irods::KW_CFG_ZONE_NAME).get()); - - if (char hostname[HOST_NAME_MAX + 1]{}; gethostname(hostname, sizeof(hostname)) == 0) { - ix::log::set_server_hostname(hostname); - } - } - - void remove_leftover_rulebase_pid_files(const nlohmann::json& _server_config) noexcept - { - namespace fs = boost::filesystem; - - try { - // Find the NREP. - const auto& plugin_config = _server_config.at(irods::KW_CFG_PLUGIN_CONFIGURATION); - const auto& rule_engines = plugin_config.at(irods::KW_CFG_PLUGIN_TYPE_RULE_ENGINE); - - const auto end = std::end(rule_engines); - const auto nrep = std::find_if(std::begin(rule_engines), end, [](const nlohmann::json& _object) { - return _object.at(irods::KW_CFG_PLUGIN_NAME).get() == "irods_rule_engine_plugin-irods_rule_language"; - }); - if (nrep == end) { - return; - } - - // Get the rulebase set. - const auto& plugin_specific_config = nrep->at(irods::KW_CFG_PLUGIN_SPECIFIC_CONFIGURATION); - const auto& rulebase_set = plugin_specific_config.at(irods::KW_CFG_RE_RULEBASE_SET); - - // Iterate over the list of rulebases and remove the leftover PID files. - for (const auto& rb : rulebase_set) { - // Create a pattern based on the rulebase's filename. The pattern will have the following format: - // - // .+/\.re\.\d+ - // - // Where is a placeholder for the target rulebase. - std::string pattern_string = ".+/"; - pattern_string += rb.get(); - pattern_string += R"_(\.re\.\d+)_"; - - const std::regex pattern{pattern_string}; - - for (const auto& p : fs::directory_iterator{irods::get_irods_config_directory()}) { - if (std::regex_match(p.path().c_str(), pattern)) { - try { - fs::remove(p); - } - catch (...) {} - } - } - } - } - catch (...) {} - } // remove_leftover_rulebase_pid_files - - void create_stacktrace_directory() - { - namespace fs = boost::filesystem; - boost::system::error_code ec; - fs::create_directories(irods::get_irods_stacktrace_directory(), ec); - } // create_stacktrace_directory - - auto get_advanced_setting(const char* _keyword, int _default, bool _negative_values_allowed = false) -> int - { - try { - const auto config_handle{irods::server_properties::instance().map()}; - const auto& config{config_handle.get_json()}; - const auto& adv_settings{config.at(irods::KW_CFG_ADVANCED_SETTINGS)}; - - const auto iter = adv_settings.find(_keyword); - - if (iter != std::end(adv_settings)) { - const auto val = iter->get(); - if (_negative_values_allowed || val > 0) { - return val; - } - } - } - catch (...) {} - - return _default; - } // get_advanced_setting - - auto get_cache_clearer_sleep_time(const char* _keyword, int _default) -> int - { - try { - const auto config_handle = irods::server_properties::instance().map(); - const auto& adv_settings = config_handle.get_json().at(irods::KW_CFG_ADVANCED_SETTINGS); - - const auto cache_type_iter = adv_settings.find(_keyword); - - if (cache_type_iter != std::end(adv_settings)) { - const auto cache_clearer_iter = - cache_type_iter->find(irods::KW_CFG_CACHE_CLEARER_SLEEP_TIME_IN_SECONDS); - - if (cache_clearer_iter != std::end(*cache_type_iter)) { - if (const auto val = cache_clearer_iter->get(); val > 0) { - return val; - } - } - } - } - catch (...) { - } - - return _default; - } // get_cache_clearer_sleep_time - - void log_stacktrace_files() - { - namespace fs = boost::filesystem; - - const auto stacktrace_directory = irods::get_irods_stacktrace_directory(); - - for (auto&& entry : fs::directory_iterator{stacktrace_directory}) { - // Expected filename format: - // - // .. - // - // 1. Extract the timestamp from the filename and convert it to ISO8601 format. - // 2. Extract the agent pid from the filename. - const auto p = entry.path().generic_string(); - - // TODO std::string::rfind can be replaced with std::string::ends_with in C++20. - if (p.rfind(irods::STACKTRACE_NOT_READY_FOR_LOGGING_SUFFIX) != std::string::npos) { - log_server::trace("Skipping [{}] ...", p); - continue; - } - - auto slash_pos = p.rfind("/"); - - if (slash_pos == std::string::npos) { - log_server::trace("Skipping [{}]. No forward slash separator found.", p); - continue; - } - - ++slash_pos; - const auto first_dot_pos = p.find(".", slash_pos); - - if (first_dot_pos == std::string::npos) { - log_server::trace("Skipping [{}]. No dot separator found.", p); - continue; - } - - const auto last_dot_pos = p.rfind("."); - - if (last_dot_pos == std::string::npos || last_dot_pos == first_dot_pos) { - log_server::trace("Skipping [{}]. No dot separator found.", p); - continue; - } - - const auto epoch_seconds = p.substr(slash_pos, first_dot_pos - slash_pos); - const auto remaining_millis = p.substr(first_dot_pos + 1, last_dot_pos - (first_dot_pos + 1)); - const auto pid = p.substr(last_dot_pos + 1); - log_server::trace( - "epoch seconds = [{}], remaining millis = [{}], agent pid = [{}]", - epoch_seconds, - remaining_millis, - pid); - - try { - // Convert the epoch value to ISO8601 format. - log_server::trace("Converting epoch seconds to UTC timestamp ..."); - using boost::chrono::system_clock; - using boost::chrono::time_fmt; - const auto tp = system_clock::from_time_t(std::stoll(epoch_seconds)); - std::ostringstream utc_ss; - utc_ss << time_fmt(boost::chrono::timezone::utc, "%FT%T") << tp; - - // Read the contents of the file. - std::ifstream file{p}; - const auto stacktrace = boost::stacktrace::stacktrace::from_dump(file); - file.close(); - - // 3. Write the contents of the stacktrace file to syslog. - irods::experimental::log::server::critical({ - {"log_message", boost::stacktrace::to_string(stacktrace)}, - {"stacktrace_agent_pid", pid}, - {"stacktrace_timestamp_utc", fmt::format("{}.{}Z", utc_ss.str(), remaining_millis)}, - {"stacktrace_timestamp_epoch_seconds", epoch_seconds}, - {"stacktrace_timestamp_epoch_milliseconds", remaining_millis} - }); - - // 4. Delete the stacktrace file. - // - // We don't want the stacktrace files to go away without making it into the log. - // We can't rely on the log invocation above because of syslog. - // We don't want these files to accumulate for long running servers. - log_server::trace("Removing stacktrace file from disk ..."); - fs::remove(entry); - } - catch (...) { - // Something happened while logging the stacktrace file. - // Leaving the stacktrace file in-place for processing later. - log_server::trace("Caught exception while processing stacktrace file."); - } - } - } // log_stacktrace_files - - void set_reload_server_config_flag(int sig) - { - reload_server_config = true; - } // set_reload_server_config_flag - - void setup_signal_handlers() noexcept - { - signal(SIGTTIN, SIG_IGN); - signal(SIGTTOU, SIG_IGN); - signal(SIGCHLD, SIG_DFL); // Setting SIGCHLD to SIG_IGN is not portable. - signal(SIGPIPE, SIG_IGN); - signal(SIGINT, serverExit); - signal(SIGHUP, set_reload_server_config_flag); - signal(SIGTERM, serverExit); - } // setup_signal_handlers - - std::optional get_grid_configuration_option_value(RcComm& _comm, - const std::string_view _namespace, - const std::string_view _option_name) - { - GridConfigurationInput input{}; - std::strcpy(input.name_space, _namespace.data()); - std::strcpy(input.option_name, _option_name.data()); - - GridConfigurationOutput* output{}; - irods::at_scope_exit free_output{[&output] { std::free(output); }}; - - if (const auto ec = rc_get_grid_configuration_value(&_comm, &input, &output); ec != 0) { - log_server::error( - "Failed to get option value from r_grid_configuration " - "[error_code={}, namespace={}, option_name={}].", - ec, - _namespace, - _option_name); - return std::nullopt; - } - - return output->option_value; - } // get_grid_configuration_option_value - - void set_delay_server_migration_info(RcComm& _comm, - const std::string_view _leader, - const std::string_view _successor) - { - DelayServerMigrationInput input{}; - std::strcpy(input.leader, _leader.data()); - std::strcpy(input.successor, _successor.data()); - - if (const auto ec = rc_set_delay_server_migration_info(&_comm, &input); ec != 0) { - log_server::error( - "Failed to set delay server migration info in r_grid_configuration " - "[error_code={}, leader={}, successor={}].", - ec, - _leader, - _successor); - } - } // set_delay_server_migration_info - - std::optional is_delay_server_running_on_remote_host(const std::string_view _hostname) - { - try { - irods::control_plane_command cpc_cmd; - cpc_cmd.command = irods::SERVER_CONTROL_STATUS; - cpc_cmd.options[irods::SERVER_CONTROL_OPTION_KW] = irods::SERVER_CONTROL_HOSTS_OPT; - cpc_cmd.options[irods::SERVER_CONTROL_HOST_KW + "0"] = _hostname; - log_server::trace("Configured control plane command."); - - rodsEnv env; - _getRodsEnv(env); - log_server::trace("Captured local environment information."); - - // Build an encryption context. - std::string_view encryption_key = env.irodsCtrlPlaneKey; - irods::buffer_crypt crypt(encryption_key.size(), - 0, - env.irodsCtrlPlaneEncryptionNumHashRounds, - env.irodsCtrlPlaneEncryptionAlgorithm); - log_server::trace("Constructed irods::buffer_crypt instance."); - - // Serialize using the generated avro class. - auto out = avro::memoryOutputStream(); - avro::EncoderPtr e = avro::binaryEncoder(); - e->init(*out); - avro::encode(*e, cpc_cmd); - std::shared_ptr> data = avro::snapshot(*out); - log_server::trace("Serialized control plane command using Avro encoder."); - - // Encrypt outgoing request. - std::vector enc_data(std::begin(*data), std::end(*data)); - irods::buffer_crypt::array_t iv; - irods::buffer_crypt::array_t in_buf(std::begin(enc_data), std::end(enc_data)); - irods::buffer_crypt::array_t shared_secret(std::begin(encryption_key), std::end(encryption_key)); - - irods::buffer_crypt::array_t data_to_send; - - if (const auto err = crypt.encrypt(shared_secret, iv, in_buf, data_to_send); !err.ok()) { - THROW(SYS_INTERNAL_ERR, err.result()); - } - - log_server::trace("Encrypted request."); - - const auto zmq_server_target = fmt::format("tcp://{}:{}", env.rodsHost, env.irodsCtrlPlanePort); - log_server::trace("Connecting to local control plane [{}] ...", zmq_server_target); - - if (!is_control_plane_accepting_requests.load()) { - log_server::info( - "Cannot determine if delay server on remote host [{}] is running at this time. " - "Control plane is not accepting requests.", - _hostname); - - // The following only applies to delay server migration. - // Return true so that the successor does not attempt to launch the delay server. - // Returning std::nullopt or false can possibly lead to the delay server being launched. - return true; - } - - zmq::context_t zmq_ctx; - zmq::socket_t zmq_socket{zmq_ctx, zmq::socket_type::req}; - const int server_control_plane_timeout_in_milliseconds = [] { - try { - const auto config_handle = irods::server_properties::instance().map(); - return config_handle.get_json().at(irods::KW_CFG_SERVER_CONTROL_PLANE_TIMEOUT).get(); - } - catch (...) { - constexpr int default_server_control_plane_timeout_in_milliseconds = 10000; - return default_server_control_plane_timeout_in_milliseconds; - } - }(); - zmq_socket.set(zmq::sockopt::sndtimeo, server_control_plane_timeout_in_milliseconds); - zmq_socket.set(zmq::sockopt::rcvtimeo, server_control_plane_timeout_in_milliseconds); - zmq_socket.set(zmq::sockopt::linger, 0); - zmq_socket.connect(zmq_server_target); - log_server::trace("Connection established."); - - log_server::trace("Sending request ..."); - zmq::message_t request(data_to_send.size()); - std::memcpy(request.data(), data_to_send.data(), data_to_send.size()); - - if (const auto res = zmq_socket.send(request, zmq::send_flags::none); !res || *res == 0) { - THROW(SYS_LIBRARY_ERROR, "zmq::socket_t::send"); - } - - log_server::trace("Sent request. Waiting for response ..."); - - zmq::message_t response; - - if (const auto res = zmq_socket.recv(response); !res || *res == 0) { - THROW(SYS_LIBRARY_ERROR, "zmq::socket_t::recv"); - } - - log_server::trace("Response received. Processing response ..."); - - // Decrypt the response. - const auto* data_ptr = static_cast(response.data()); - in_buf.clear(); - in_buf.assign(data_ptr, data_ptr + response.size()); - - irods::buffer_crypt::array_t dec_data; - - if (const auto err = crypt.decrypt(shared_secret, iv, in_buf, dec_data); !err.ok()) { - THROW(SYS_INTERNAL_ERR, err.result()); - } - - log_server::trace("Decrypted response."); - - std::string response_string(dec_data.data(), dec_data.data() + dec_data.size()); - log_server::trace("Control plane response ==> [{}]", response_string); - - if (const auto pos = response_string.find_last_of(","); pos != std::string::npos) { - response_string[pos] = ' '; - } - - const auto grid_status = nlohmann::json::parse(response_string); - log_server::trace("Control plane response ==> [{}]", grid_status.dump()); - - return grid_status.at("delay_server_pid").get() > 0; - } - catch (const zmq::error_t& e) { - log_server::error("ZMQ error: {}", e.what()); - } - catch (const std::exception& e) { - log_server::error("General exception: {}", e.what()); - } - - return std::nullopt; - } // is_delay_server_running_on_remote_host - - void launch_delay_server(bool _enable_test_mode, bool _write_to_stdout) - { - log_server::info("Forking Delay Server (irodsDelayServer) ..."); - - // If we're planning on calling one of the functions from the exec-family, - // then we're only allowed to use async-signal-safe functions following the call - // to fork(). To avoid potential issues, we build up the argument list before - // doing the fork-exec. - - std::vector args{"irodsDelayServer"}; - - if (_enable_test_mode) { - args.push_back("-t"); - } - - if (_write_to_stdout) { - args.push_back("-u"); - } - - args.push_back(nullptr); - - if (fork() == 0) { - execv(args[0], args.data()); - - // If execv() fails, the POSIX standard recommends using _exit() instead of exit() to avoid - // flushing stdio buffers and handlers registered by the parent. - // - // In the case of C++, this is necessary to avoid triggering destructors. Triggering a destructor - // could result in assertions made by the struct/class being violatied. For some data types, - // violating an assertion results in program termination (i.e. SIGABRT). - _exit(1); - } - } // launch_delay_server - - std::string get_preferred_hostname(const std::string_view _hostname) - { - if (const auto hn = resolve_hostname(_hostname, hostname_resolution_scheme::match_preferred); hn) { - return *hn; - } - - return std::string{_hostname}; - } // get_preferred_hostname - - void migrate_delay_server(bool _enable_test_mode, bool _write_to_stdout) - { - try { - if (!is_control_plane_accepting_requests.load()) { - return; - } - - // At this point, we don't have a database connection because grandpa never needed - // one. initServer() calls initServerInfo() which attempts to establish a database connection. - // initServer() immediately tears down the database connection. We can only guess that this is - // done because grandpa never accesses the database. - // - // The lines that follow temporarily re-establish a database connection only if the local - // server is a catalog provider. This is necessary because none of the APIs invoked after this - // database code results in the creation of a database connection. - - std::string service_role; - if (const auto err = get_catalog_service_role(service_role); !err.ok()) { - log_server::error("Could not retrieve server role [error_code={}].", err.code()); - return; - } - - const auto is_provider = (irods::KW_CFG_SERVICE_ROLE_PROVIDER == service_role); - - irods::at_scope_exit disconnect_from_database{[is_provider] { - if (is_provider) { - if (const auto ec = chlClose(); ec != 0) { - log_server::error("Could not disconnect from database [error_code={}].", ec); - } - } - }}; - - if (is_provider) { - // Assume this server has database credentials and attempt to connect to the database. - if (const auto ec = chlOpen(); ec != 0) { - log_server::error("Could not connect to database [error_code={}].", ec); - return; - } - } - - std::string hostname; - - { - char hn[HOST_NAME_MAX + 1]{}; - - if (gethostname(hn, sizeof(hn)) != 0) { - log_server::error("Failed to retrieve local server's hostname for delay server migration."); - return; - } - - hostname = get_preferred_hostname(hn); - } - - log_server::trace("Local server's hostname = [{}]", hostname); - - std::string leader; - std::string successor; - - { - namespace ss = irods::server_state; - - // Make sure the server is accepting requests before attempting to establish a connection. - // The delay server migration logic runs in a separate thread. When the server is instructed - // to shutdown, it's possible for the delay server migration logic to run during that time. - // This can lead to errors appearing in the log file. The errors aren't harmful to the system, - // but we want to avoid there if possible. This check helps this situation by shrinking the window. - if (const auto state = ss::get_state(); state != ss::server_state::running) { - log_server::trace("Cannot establish client connection to local server at this time. " - "Server is either shutting down or is paused."); - return; - } - - ix::client_connection comm; - - auto result = get_grid_configuration_option_value(comm, "delay_server", "leader"); - if (!result) { - return; - } - - leader = get_preferred_hostname(*result); - log_server::trace("Leader's hostname = [{}]", leader); - - result = get_grid_configuration_option_value(comm, "delay_server", "successor"); - if (!result) { - return; - } - - successor = get_preferred_hostname(*result); - log_server::trace("Successor's hostname = [{}]", successor); - } - - if (hostname == leader) { - log_server::trace("Local server [{}] is the leader.", hostname); - - if (!successor.empty() && hostname != successor) { - log_server::trace("New successor detected!"); - - const auto pid = irods::get_pid_from_file(irods::PID_FILENAME_DELAY_SERVER); - - if (!pid) { - log_server::trace("Could not get delay server PID."); - return; - } - - log_server::trace("Delay server PID = [{}]", *pid); - log_server::info("Sending shutdown signal to delay server."); - - // If the delay server is running locally, send SIGTERM to it and wait - // for graceful shutdown. The call to waitpid() will cause the CRON manager - // to stop making progress until it returns because it is single threaded. - kill(*pid, SIGTERM); - - int child_status = 0; - waitpid(*pid, &child_status, 0); - log_server::info("Delay server has completed shutdown [exit_code={}].", WEXITSTATUS(child_status)); - } - else { - log_server::trace("No successor detected. Checking if delay server needs to be launched ..."); - - // If the delay server is not running, launch it! - if (const auto pid = irods::get_pid_from_file(irods::PID_FILENAME_DELAY_SERVER); !pid) { - launch_delay_server(_enable_test_mode, _write_to_stdout); - } - } - } - else if (hostname == successor) { - log_server::trace("Local server [{}] is the successor.", hostname); - - const auto promote_to_leader_and_launch_delay_server = [_enable_test_mode, _write_to_stdout, &hostname] { - failed_delay_server_migration_communication_attempts = 0; - - { - namespace ss = irods::server_state; - - // Check the server's state before attempting to establish a client connection. - // See identical check above for more information on why this is necessary. - if (const auto state = ss::get_state(); state != ss::server_state::running) { - log_server::trace("Cannot establish client connection to local server at this time. " - "Server is either shutting down or is paused."); - return; - } - - ix::client_connection comm; - set_delay_server_migration_info(comm, hostname, ""); - } - - launch_delay_server(_enable_test_mode, _write_to_stdout); - }; - - // Wait for the leader (remote host) to shutdown its delay server. - // - // Because we're relying on the network to determine when it is safe to launch our own - // delay server, we have to consider the case where network communication fails. - // - // For this situation, we keep a count of the number of failed communication attempts. - // When the threshold for failed attempts is reached, the local server will be promoted - // to the leader. - if (const auto res = is_delay_server_running_on_remote_host(leader); res && !*res) { - promote_to_leader_and_launch_delay_server(); - } - // Automatically promote this server to the leader after three failed communication attempts. - else if (++failed_delay_server_migration_communication_attempts == 3) { - promote_to_leader_and_launch_delay_server(); - } - } - } - catch (const std::exception& e) { - log_server::error("Caught exception in migrate_delay_server(): {}", e.what()); - } - catch (...) { - log_server::error("Caught unknown exception in migrate_delay_server()"); - } - } // migrate_delay_server - - void daemonize() - { - if (fork()) { - // End the parent process immediately. - exit(0); - } - - if (setsid() < 0) { - log_server::error("daemonize: setsid failed, errno = {}\n", errno); - exit(1); - } - - close(0); - close(1); - close(2); - - open("/dev/null", O_RDONLY); - open("/dev/null", O_WRONLY); - open("/dev/null", O_RDWR); - } // daemonize - - void print_usage(const char* prog) - { - fmt::print("Usage: {} [-uvVqs]\n", prog); - fmt::print(" -u user command level, remain attached to the tty (foreground)\n"); - fmt::print(" -v verbose (LOG_NOTICE)\n"); - fmt::print(" -V very verbose (LOG_DEBUG10)\n"); - fmt::print(" -q quiet (LOG_ERROR)\n"); - fmt::print(" -s log SQL commands\n"); - } - - int purgeLockFileDir(int chkLockFlag) - { - char lockFilePath[MAX_NAME_LEN * 2]; - struct dirent* myDirent; - struct stat statbuf; - int status; - int savedStatus = 0; - struct flock myflock; - unsigned int purgeTime; - - std::string lock_dir; - irods::error ret = irods::get_full_path_for_unmoved_configs(LOCK_FILE_DIR, lock_dir); - if (!ret.ok()) { - irods::log(PASS(ret)); - return ret.code(); - } - - DIR* dirPtr = opendir(lock_dir.c_str()); - if (dirPtr == NULL) { - log_server::error("purgeLockFileDir: opendir error for [{}], errno = [{}]", lock_dir.c_str(), errno); - return UNIX_FILE_OPENDIR_ERR - errno; - } - - std::memset(&myflock, 0, sizeof(myflock)); - myflock.l_whence = SEEK_SET; - purgeTime = time(0) - LOCK_FILE_PURGE_TIME; - while ((myDirent = readdir(dirPtr)) != NULL) { - if (strcmp(myDirent->d_name, ".") == 0 || strcmp(myDirent->d_name, "..") == 0) { - continue; - } - snprintf(lockFilePath, MAX_NAME_LEN, "%-s/%-s", lock_dir.c_str(), myDirent->d_name); - if (chkLockFlag) { - int myFd; - myFd = open(lockFilePath, O_RDWR | O_CREAT, 0644); - if (myFd < 0) { - savedStatus = FILE_OPEN_ERR - errno; - rodsLogError(LOG_ERROR, savedStatus, "purgeLockFileDir: open error for [{}]", lockFilePath); - continue; - } - myflock.l_type = F_WRLCK; - int error_code = fcntl(myFd, F_GETLK, &myflock); - if (error_code != 0) { - log_server::error("fcntl failed in purgeLockFileDir with error code [{}]", error_code); - } - close(myFd); - /* some process is locking it */ - if (myflock.l_type != F_UNLCK) { - continue; - } - } - status = stat(lockFilePath, &statbuf); - - if (status != 0) { - log_server::error("purgeLockFileDir: stat error for [{}], errno = [{}]", lockFilePath, errno); - savedStatus = UNIX_FILE_STAT_ERR - errno; - boost::system::error_code err; - boost::filesystem::remove(boost::filesystem::path(lockFilePath), err); - continue; - } - if (chkLockFlag && (int) purgeTime < statbuf.st_mtime) { - continue; - } - if ((statbuf.st_mode & S_IFREG) == 0) { - continue; - } - boost::system::error_code err; - boost::filesystem::remove(boost::filesystem::path(lockFilePath), err); - } - closedir(dirPtr); - - return savedStatus; - } // purgeLockFileDir - - void task_purge_lock_file() - { - std::size_t wait_time_ms = 0; - const std::size_t purge_time_ms = LOCK_FILE_PURGE_TIME * 1000; // s to ms - - while (true) { - const auto state = irods::server_state::get_state(); - - if (irods::server_state::server_state::stopped == state || - irods::server_state::server_state::exited == state) { - break; - } - - rodsSleep(0, irods::SERVER_CONTROL_POLLING_TIME_MILLI_SEC * 1000); // second, microseconds - wait_time_ms += irods::SERVER_CONTROL_POLLING_TIME_MILLI_SEC; - - if (wait_time_ms >= purge_time_ms) { - wait_time_ms = 0; - int status = purgeLockFileDir(1); - if (status < 0) { - rodsLogError(LOG_ERROR, status, "task_purge_lock_file: purgeLockFileDir failed"); - } - } - } - } // task_purge_lock_file - - int queueAgentProc(agentProc_t* agentProc, agentProc_t** agentProcHead, irodsPosition_t position) - { - if (agentProc == NULL || agentProcHead == NULL) { - return USER__NULL_INPUT_ERR; - } - - if (*agentProcHead == NULL) { - *agentProcHead = agentProc; - agentProc->next = NULL; - return 0; - } - - if (position == TOP_POS) { - agentProc->next = *agentProcHead; - *agentProcHead = agentProc; - } - else { - agentProc_t* tmpAgentProc = *agentProcHead; - while (tmpAgentProc->next != NULL) { - tmpAgentProc = tmpAgentProc->next; - } - tmpAgentProc->next = agentProc; - agentProc->next = NULL; - } - return 0; - } // queueAgentProc - - void launch_read_worker_threads() - { - for (int i = 0; i < NUM_READ_WORKER_THR; ++i) { - try { - ReadWorkerThread[i] = new boost::thread(readWorkerTask); - } - catch (const boost::thread_resource_error&) { - THROW(SYS_THREAD_RESOURCE_ERR, "Error launching read worker threads."); - } - } - } // launch_read_worker_threads - - void launch_spawn_manager_thread() - { - try { - SpawnManagerThread = new boost::thread(task_spawn_manager); - } - catch (const boost::thread_resource_error&) { - THROW(SYS_THREAD_RESOURCE_ERR, "Error launching spawn management thread."); - } - } // launch_spawn_manager_thread - - void launch_purge_lock_file_thread(std::string_view _server_role) - { - // Start purge lock file thread - if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == _server_role) { - try { - PurgeLockFileThread = new boost::thread(task_purge_lock_file); - } - catch (const boost::thread_resource_error&) { - log_server::error("{}: Error launching thread.", __func__); - } - } - } // launch_purge_lock_file_thread - - void join_purge_lock_file_thread(std::string_view _server_role) - { - if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == _server_role) { - try { - PurgeLockFileThread->join(); - } - catch (const boost::thread_resource_error&) { - log_server::error("{}: Error joining thread.", __func__); - } - } - } // join_purge_lock_file_thread - - void join_spawn_manager_thread() - { - SpawnReqCond.notify_all(); - - try { - SpawnManagerThread->join(); - } - catch (const boost::thread_resource_error&) { - log_server::error("{}: Error joining thread.", __func__); - } - } // join_spawn_manager_thread - - void join_read_worker_threads() - { - for (int i = 0; i < NUM_READ_WORKER_THR; ++i) { - ReadReqCond.notify_all(); - - try { - ReadWorkerThread[i]->join(); - } - catch (const boost::thread_resource_error&) { - log_server::error("{}: Error joining thread.", __func__); - } - } - } // join_read_worker_threads -} // anonymous namespace - -int main(int argc, char** argv) -{ - int c; - char tmpStr1[100]; - char tmpStr2[100]; - bool write_to_stdout = false; - bool enable_test_mode = false; - - ProcessType = SERVER_PT; // This process identifies itself as a server. - - if (const char* log_level = getenv(SP_LOG_LEVEL); log_level) { - rodsLogLevel(atoi(log_level)); - } - else { - rodsLogLevel(LOG_NOTICE); - } - - if (const char* sql_log_level = std::getenv(SP_LOG_SQL); sql_log_level) { - rodsLogSqlReq(1); - } - - ServerBootTime = std::time(nullptr); - - while ((c = getopt(argc, argv, "tuvVqsh")) != EOF) { - switch (c) { - case 't': - enable_test_mode = true; - break; - - // Write to stdout - do not daemonize process. - case 'u': - write_to_stdout = true; - break; - - // Enable verbose logging. - case 'v': - std::snprintf(tmpStr1, 100, "%s=%d", SP_LOG_LEVEL, LOG_NOTICE); - putenv(tmpStr1); - rodsLogLevel(LOG_NOTICE); - break; - - // Enable very verbose logging. - case 'V': - std::snprintf(tmpStr1, 100, "%s=%d", SP_LOG_LEVEL, LOG_DEBUG10); - putenv(tmpStr1); - rodsLogLevel(LOG_DEBUG10); - break; - - // Quiet (only log errors and above). - case 'q': - std::snprintf(tmpStr1, 100, "%s=%d", SP_LOG_LEVEL, LOG_ERROR); - putenv(tmpStr1); - rodsLogLevel(LOG_ERROR); - break; - - // Log SQL commands. - case 's': - std::snprintf(tmpStr2, 100, "%s=%d", SP_LOG_SQL, 1); - putenv(tmpStr2); - break; - - // Help - case 'h': - print_usage(argv[0]); - exit(0); - - default: - print_usage(argv[0]); - exit(1); - } - } - - if (!write_to_stdout) { - daemonize(); - } - - auto server_config = load_server_config(); - if (!server_config) { - return 1; - } - - init_logger(*server_config, write_to_stdout, enable_test_mode); - - log_server::info("Initializing server ..."); - - const auto pid_file_fd = irods::create_pid_file(irods::PID_FILENAME_MAIN_SERVER); - if (pid_file_fd == -1) { - return 1; - } - - irods::client_api_allowlist::init(); - - irods::at_scope_exit deinit_server_state{[] { irods::server_state::deinit(); }}; - irods::server_state::init(); - irods::server_state::set_state(irods::server_state::server_state::running); - - create_stacktrace_directory(); - - namespace hnc = irods::experimental::net::hostname_cache; - hnc::init("irods_hostname_cache", irods::get_hostname_cache_shared_memory_size()); - irods::at_scope_exit deinit_hostname_cache{[] { hnc::deinit(); }}; - - namespace dnsc = irods::experimental::net::dns_cache; - dnsc::init("irods_dns_cache", irods::get_dns_cache_shared_memory_size()); - irods::at_scope_exit deinit_dns_cache{[] { dnsc::deinit(); }}; - - ix::replica_access_table::init(); - irods::at_scope_exit deinit_replica_access_table{[] { ix::replica_access_table::deinit(); }}; - - remove_leftover_rulebase_pid_files(*server_config); - - // Clear the temporary server config object used for initialization. - // After this point, everything will rely on the irods::server_properties interface for - // information defined in server_config.json. - server_config.reset(); - - irods::server_properties::instance().capture(); - - using key_path_t = irods::configuration_parser::key_path_t; - - // Set the default value for evicting DNS cache entries. - irods::set_server_property( - key_path_t{irods::KW_CFG_ADVANCED_SETTINGS, irods::KW_CFG_DNS_CACHE, irods::KW_CFG_EVICTION_AGE_IN_SECONDS}, - irods::get_dns_cache_eviction_age()); - - // Set the default value for evicting hostname cache entries. - irods::set_server_property( - key_path_t{irods::KW_CFG_ADVANCED_SETTINGS, irods::KW_CFG_HOSTNAME_CACHE, irods::KW_CFG_EVICTION_AGE_IN_SECONDS}, - irods::get_hostname_cache_eviction_age()); - - setup_signal_handlers(); - - // Create a directory for IPC related sockets. This directory protects IPC sockets from - // external applications. - if (!mkdtemp(unix_domain_socket_directory)) { - log_server::error("Error creating temporary directory for iRODS sockets, mkdtemp errno [{}]: [{}].", - errno, - strerror(errno)); - return SYS_INTERNAL_ERR; - } - - log_server::info("Setting up UNIX domain socket for agent factory ..."); - char random_suffix[65]; - get64RandomBytes(random_suffix); - std::snprintf(agent_factory_socket_file, sizeof(agent_factory_socket_file), "%s/irods_factory_%s", unix_domain_socket_directory, random_suffix); - - // Set up local_addr for socket communication. - local_addr.sun_family = AF_UNIX; - std::snprintf(local_addr.sun_path, sizeof(local_addr.sun_path), "%s", agent_factory_socket_file); - - const auto launch_agent_factory = [&] { - try { - // Return immediately if the agent factory exists. - // When the server is starting up, agent_spawning_pid will be zero. Therefore, - // we skip checking for the agent factory on startup. - if (agent_spawning_pid > 0 && waitpid(agent_spawning_pid, nullptr, WNOHANG) != -1) { - return; - } - - log_server::info("Forking agent factory ..."); - - agent_spawning_pid = fork(); - - if (agent_spawning_pid == 0) { - close(pid_file_fd); - - ProcessType = AGENT_PT; - - try { - const auto ec = runIrodsAgentFactory(local_addr); - - log_server::critical("Agent factory returned with error code [{}].", ec); - -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - // This function must be called here due to the use of _exit() (just below). Address Sanitizer - // (ASan) relies on std::atexit handlers to report its findings. _exit() does not trigger any - // of the handlers registered by ASan, therefore, we manually run ASan just before the agent - // factory exits. - __lsan_do_leak_check(); -#endif - - // Collapse non-zero error codes into one. - // - // The agent factory is normally shut down via the SIGTERM signal. However, if the agent factory - // fails to fork an agent, it will return SYS_FORK_ERROR here. - // - // If the agent factory terminates for whatever reason, is respawned by the main server process, - // and then fails to fork an agent, the agent factory will most likely exit with a SIGABRT due to - // a failed assertion in boost::mutex or boost::condition_variable. Destructing a mutex while it - // is locked is not allowed by the Boost.Thread library. - // - // The only way to avoid this situation is to use _exit(). Using _exit() is safe here because this - // is the final step in shutting down the agent factory process. - _exit(ec == 0 ? 0 : 1); - } - catch (...) { - // If an exception is thrown for any reason, terminate the agent factory. - _exit(1); - } - } - else if (agent_spawning_pid > 0) { - // If the agent factory is no longer available (e.g. it crashed or something), close the - // socket associated with the crashed agent factory. - close(agent_conn_socket); - - log_server::info("Connecting to agent factory [agent_factory_pid={}] ...", agent_spawning_pid); - - agent_conn_socket = socket(AF_UNIX, SOCK_STREAM, 0); - - const auto start_time = std::time(nullptr); - - // Loop until grandpa establishes a connection to the new agent factory. - // If more than five seconds have elapsed, perhaps something bad has happened. - // In this case, just terminate the program. - while (true) { - if (connect(agent_conn_socket, (const struct sockaddr*) &local_addr, sizeof(local_addr)) >= 0) { - break; - } - - const auto saved_errno = errno; - - if ((std::time(nullptr) - start_time) > 5) { - log_server::error("Error connecting to agent factory socket, errno = [{}]: {}", - saved_errno, - strerror(saved_errno)); - exit(SYS_SOCK_CONNECT_ERR); - } - } - } - } - catch (...) { - // Do not allow exceptions to escape the CRON task! - // Failing to do so can cause the CRON manager thread to crash the server. - } - }; // launch_agent_factory - - launch_agent_factory(); - ix::cron::cron_builder agent_watcher; - const auto agent_factory_watcher_sleep_time_in_seconds = - get_advanced_setting(irods::KW_CFG_AGENT_FACTORY_WATCHER_SLEEP_TIME_IN_SECONDS, 5); - agent_watcher.interval(agent_factory_watcher_sleep_time_in_seconds).task(launch_agent_factory); - ix::cron::cron::instance().add_task(agent_watcher.build()); - - { - ix::cron::cron_builder cache_clearer; - cache_clearer.interval(get_cache_clearer_sleep_time(irods::KW_CFG_DNS_CACHE, 600)).task([] { - log_server::trace("Expiring old DNS cache entries"); - irods::experimental::net::dns_cache::erase_expired_entries(); - }); - ix::cron::cron::instance().add_task(cache_clearer.build()); - } - - { - ix::cron::cron_builder cache_clearer; - cache_clearer.interval(get_cache_clearer_sleep_time(irods::KW_CFG_HOSTNAME_CACHE, 600)).task([] { - log_server::trace("Expiring old hostname cache entries"); - irods::experimental::net::hostname_cache::erase_expired_entries(); - }); - ix::cron::cron::instance().add_task(cache_clearer.build()); - } - - int acceptErrCnt = 0; - - // set re cache salt here - irods::error ret = createAndSetRECacheSalt(); - if (!ret.ok()) { - log_server::error("{}: createAndSetRECacheSalt error.\n{}", __func__, ret.result()); - exit(1); - } - - ret = init_shared_memory_for_plugins(); - if (!ret.ok()) { - irods::log(PASS(ret)); - } - - irods::at_scope_exit remove_shared_memory{[] { deinit_shared_memory_for_plugins(); }}; - - irods::re_plugin_globals.reset(new irods::global_re_plugin_mgr); - - rsComm_t svrComm; - int status = initServerMain(&svrComm, enable_test_mode, write_to_stdout); - if (status < 0) { - log_server::error("{}: initServerMain error. status = {}", __func__, status); - exit(1); - } - - std::string svc_role; - ret = get_catalog_service_role(svc_role); - if (!ret.ok()) { - irods::log(PASS(ret)); - return ret.code(); - } - - // Add the stacktrace file processor task to the CRON manager. - { - ix::cron::cron_builder task_builder; - const auto stacktrace_file_processor_sleep_time = - get_advanced_setting(irods::KW_CFG_STACKTRACE_FILE_PROCESSOR_SLEEP_TIME_IN_SECONDS, 10); - task_builder.interval(stacktrace_file_processor_sleep_time).task(log_stacktrace_files); - ix::cron::cron::instance().add_task(task_builder.build()); - } - - { - ix::cron::cron_builder configuration_reloader; - configuration_reloader.interval(0).task([]() { - if (reload_server_config) { - reload_server_config = false; - auto diff = irods::server_properties::instance().reload(); - ix::json_events::run_hooks(diff); - log_server::info("Configuration Reloaded."); - } - }); - } - - std::thread cron_manager_thread{[] { - while (true) { - try { - const auto state = irods::server_state::get_state(); - - if (irods::server_state::server_state::stopped == state || - irods::server_state::server_state::exited == state) - { - break; - } - - using namespace std::chrono_literals; - std::this_thread::sleep_for(100ms); - - ix::cron::cron::instance().run(); - } - catch (...) {} - } - }}; - - irods::at_scope_exit join_cron_manager_thread{[&] { cron_manager_thread.join(); }}; - - std::uint64_t return_code = 0; - - try { - // Launch the control plane. - irods::server_control_plane ctrl_plane(irods::KW_CFG_SERVER_CONTROL_PLANE_PORT, is_control_plane_accepting_requests); - - launch_read_worker_threads(); - launch_spawn_manager_thread(); - launch_purge_lock_file_thread(svc_role); - - fd_set sockMask; - FD_ZERO(&sockMask); - SvrSock = svrComm.sock; - - while (true) { - const auto state = irods::server_state::get_state(); - - if (irods::server_state::server_state::stopped == state) { - procChildren(&ConnectedAgentHead); - - // Shut down the agent factory. - kill(agent_spawning_pid, SIGTERM); - int agent_factory_status = 0; - waitpid(agent_spawning_pid, &agent_factory_status, 0); - log_server::info( - "Agent factory has completed shutdown [exit_code={}].", WEXITSTATUS(agent_factory_status)); - - log_server::info("iRODS Server is exiting with state [{}].", to_string(state)); - break; - } - - if (irods::server_state::server_state::paused == state) { - procChildren(&ConnectedAgentHead); - rodsSleep(0, irods::SERVER_CONTROL_FWD_SLEEP_TIME_MILLI_SEC * 1000); - continue; - } - - if (irods::server_state::server_state::running != state) { - log_server::info("Invalid iRODS server state [{}].", to_string(state)); - } - FD_SET(svrComm.sock, &sockMask); - - int numSock = 0; - struct timeval time_out; - time_out.tv_sec = 0; - time_out.tv_usec = irods::SERVER_CONTROL_POLLING_TIME_MILLI_SEC * 1000; - - while ((numSock = select(svrComm.sock + 1, &sockMask, nullptr, nullptr, &time_out)) < 0) { - if (errno == EINTR) { - log_server::info("{}: select() interrupted", __func__); - FD_SET(svrComm.sock, &sockMask); - continue; - } - - log_server::info("{}: select() error, errno = {}", __func__, errno); - return -1; - } - - if (reload_server_config) { - // As a side effect of reading the server properties object, we acquire a lock - // that cannot be had while the configuration is in the middle of reloading. - // - // This is important because otherwise the server may respond to a new request - // before loading the new settings. - irods::server_properties::instance().contains(""); - } - - procChildren(&ConnectedAgentHead); - - if (0 == numSock) { - continue; - } - - const int newSock = rsAcceptConn(&svrComm); - if (newSock < 0) { - if (++acceptErrCnt > MAX_ACCEPT_ERR_CNT) { - log_server::error("{}: Too many socket accept error. Exiting", __func__); - break; - } - - log_server::info("{}: acceptConn() error, errno = {}", __func__, errno); - continue; - } - else { - acceptErrCnt = 0; - } - - status = chkAgentProcCnt(); - if (status < 0) { - log_server::info("{}: chkAgentProcCnt failed status = {}", __func__, status); - - // =-=-=-=-=-=-=- - // create network object to communicate to the network - // plugin interface. repave with newSock as that is the - // operational socket at this point - - irods::network_object_ptr net_obj; - irods::error ret = irods::network_factory( &svrComm, net_obj ); - - if (!ret.ok()) { - irods::log(PASS(ret)); - } - else { - ret = sendVersion(net_obj, status, 0, nullptr, 0); - if (!ret.ok()) { - irods::log(PASS(ret)); - } - } - - status = mySockClose(newSock); - printf("close status = %d\n", status); - continue; - } - - addConnReqToQueue(&svrComm, newSock); - } - - join_purge_lock_file_thread(svc_role); - - procChildren(&ConnectedAgentHead); - - join_spawn_manager_thread(); - join_read_worker_threads(); - - irods::server_state::set_state(irods::server_state::server_state::exited); - } - catch (const irods::exception& e) { - log_server::error("Exception caught in server loop\n{}", e.what()); - return_code = e.code(); - } - - close(agent_conn_socket); - unlink(agent_factory_socket_file); - rmdir(unix_domain_socket_directory); - - log_server::info("iRODS Server is done."); - - return return_code; -} - -void serverExit(int _signal) -{ - recordServerProcess(nullptr); // Unlink the process id file. - - close(agent_conn_socket); - unlink(agent_factory_socket_file); - rmdir(unix_domain_socket_directory); - - // Wake and terminate agent spawning process - kill(agent_spawning_pid, SIGTERM); - -#if __has_feature(address_sanitizer) || defined(__SANITIZE_ADDRESS__) - // Calling this function is likely not async-signal-safe, but it is okay because - // the code has been compiled with Address Sanitizer enabled. For that reason, - // we can assume that the binary is not running in a production environment. - __lsan_do_leak_check(); -#endif - - _exit(_signal); -} - -int procChildren(agentProc_t** agentProcHead) -{ - boost::unique_lock con_agent_lock(ConnectedAgentMutex); - - agentProc_t* tmpAgentProc = *agentProcHead; - agentProc_t* finishedAgentProc{}; - agentProc_t* prevAgentProc{}; - - while (tmpAgentProc) { - // Check if pid is still an active process - if (kill(tmpAgentProc->pid, 0)) { - finishedAgentProc = tmpAgentProc; - - if (!prevAgentProc) { - *agentProcHead = tmpAgentProc->next; - } - else { - prevAgentProc->next = tmpAgentProc->next; - } - - tmpAgentProc = tmpAgentProc->next; - std::free(finishedAgentProc); - } - else { - prevAgentProc = tmpAgentProc; - tmpAgentProc = tmpAgentProc->next; - } - } - - con_agent_lock.unlock(); - - return 0; -} - -agentProc_t* getAgentProcByPid(int childPid, agentProc_t** agentProcHead) -{ - boost::unique_lock con_agent_lock(ConnectedAgentMutex); - - agentProc_t* tmpAgentProc = *agentProcHead; - agentProc_t* prevAgentProc{}; - - while (tmpAgentProc) { - if (childPid == tmpAgentProc->pid) { - if (!prevAgentProc) { - *agentProcHead = tmpAgentProc->next; - } - else { - prevAgentProc->next = tmpAgentProc->next; - } - - break; - } - - prevAgentProc = tmpAgentProc; - tmpAgentProc = tmpAgentProc->next; - } - - con_agent_lock.unlock(); - - return tmpAgentProc; -} - -int queueConnectedAgentProc(int childPid, agentProc_t* connReq, agentProc_t** agentProcHead) -{ - if (!connReq) { - return USER__NULL_INPUT_ERR; - } - - connReq->pid = childPid; - - boost::unique_lock con_agent_lock(ConnectedAgentMutex); - queueAgentProc(connReq, agentProcHead, TOP_POS); - con_agent_lock.unlock(); - - return 0; -} - -int spawnAgent(agentProc_t* connReq, agentProc_t** agentProcHead) -{ - if (!connReq) { - return USER__NULL_INPUT_ERR; - } - - const int newSock = connReq->sock; - startupPack_t* startupPack = &connReq->startupPack; - - const int childPid = execAgent(newSock, startupPack); - if (childPid > 0) { - queueConnectedAgentProc(childPid, connReq, agentProcHead); - } - - return childPid; -} - -int sendEnvironmentVarIntToSocket(const char* var, int val, int socket) -{ - const auto msg = fmt::format("{}={};", var, val); - const ssize_t status = send(socket, msg.c_str(), msg.size(), 0); - - if (status < 0) { - log_server::error("Error in sendEnvironmentVarIntToSocket, errno = [{}]: {}", errno, strerror(errno)); - } - else if (static_cast(status) != msg.size()) { - log_server::debug("Failed to send entire message in sendEnvironmentVarIntToSocket - msg [{}] is [{}] " - "bytes long, sent [{}] bytes", - msg.c_str(), - msg.size(), - status); - } - - return status; -} - -int sendEnvironmentVarStrToSocket(const char* var, const char* val, int socket) -{ - const auto msg = fmt::format("{}={};", var, val); - const ssize_t status = send(socket, msg.c_str(), msg.size(), 0); - - if (status < 0) { - log_server::error("Error in sendEnvironmentVarIntToSocket, errno = [{}]: {}", errno, strerror(errno)); - } - else if (static_cast(status) != msg.size()) { - log_server::debug("Failed to send entire message in sendEnvironmentVarIntToSocket - msg [{}] is [{}] " - "bytes long, sent [{}] bytes", - msg.c_str(), - msg.size(), - status); - } - - return status; -} - -ssize_t sendSocketOverSocket(int writeFd, int socket) -{ - struct msghdr msg; - struct iovec iov[1]; - - union { - struct cmsghdr cm; - char control[CMSG_SPACE(sizeof(int))]; - } control_un; - struct cmsghdr *cmptr; - - std::memset(control_un.control, 0, sizeof(control_un.control)); - msg.msg_control = control_un.control; - msg.msg_controllen = sizeof(control_un.control); - - cmptr = CMSG_FIRSTHDR(&msg); - cmptr->cmsg_len = CMSG_LEN(sizeof(int)); - cmptr->cmsg_level = SOL_SOCKET; - cmptr->cmsg_type = SCM_RIGHTS; - *((int *) CMSG_DATA(cmptr)) = socket; - - msg.msg_name = nullptr; - msg.msg_namelen = 0; - - iov[0].iov_base = (void*) "i"; - iov[0].iov_len = 1; - msg.msg_iov = iov; - msg.msg_iovlen = 1; - - return sendmsg(writeFd, &msg, 0); -} - -int execAgent(int newSock, startupPack_t* startupPack) -{ - // Create unique socket for each call to exec agent - char random_suffix[65]{}; - get64RandomBytes(random_suffix); - - sockaddr_un tmp_socket_addr{}; - char tmp_socket_file[sizeof(tmp_socket_addr.sun_path)]{}; - std::snprintf(tmp_socket_file, - sizeof(tmp_socket_file), - "%s/irods_agent_%s", - unix_domain_socket_directory, - random_suffix); - - ssize_t status = send(agent_conn_socket, tmp_socket_file, strlen(tmp_socket_file), 0); - if (status < 0) { - log_server::error("Error sending socket to agent factory process, errno = [{}]: {}", errno, strerror(errno)); - } - else if (static_cast(status) < std::strlen(tmp_socket_file)) { - log_server::debug("Failed to send entire message - msg [{}] is [{}] bytes long, sent [{}] bytes", - tmp_socket_file, - strnlen(tmp_socket_file, sizeof(tmp_socket_file)), - status); - } - - tmp_socket_addr.sun_family = AF_UNIX; - strncpy(tmp_socket_addr.sun_path, tmp_socket_file, sizeof(tmp_socket_addr.sun_path)); - - int tmp_socket{socket(AF_UNIX, SOCK_STREAM, 0)}; - if (tmp_socket < 0) { - log_server::error("Unable to create socket in execAgent, errno = [{}]: {}", errno, strerror(errno)); - } - - const auto cleanup_sockets{[&]() { - if (close(tmp_socket) < 0) { - log_server::error("close(tmp_socket) failed with errno = [{}]: {}", errno, strerror(errno)); - } - - if (unlink(tmp_socket_file) < 0) { - log_server::error("unlink(tmp_socket_file) failed with errno = [{}]: {}", errno, strerror(errno)); - } - }}; - - // Wait until receiving acknowledgement that socket has been created - char in_buf[1024]{}; - status = recv(agent_conn_socket, &in_buf, sizeof(in_buf), 0); - if (status < 0) { - log_server::error("Error in recv acknowledgement from agent factory process, errno = [{}]: {}", - errno, - strerror(errno)); - status = SYS_SOCK_READ_ERR; - } - else if (0 != strcmp(in_buf, "OK")) { - log_server::error("Bad acknowledgement from agent factory process, message = [{}]", in_buf); - status = SYS_SOCK_READ_ERR; - } - else { - status = connect(tmp_socket, (const struct sockaddr*) &tmp_socket_addr, sizeof(local_addr)); - if (status < 0) { - log_server::error("Unable to connect to socket in agent factory process, errno = [{}]: {}", - errno, - strerror(errno)); - status = SYS_SOCK_CONNECT_ERR; - } - } - - if (status < 0) { - // Agent factory expects a message about connection to the agent - send failure - const std::string failure_message{"spawn_failure"}; - send(agent_conn_socket, failure_message.c_str(), failure_message.length() + 1, 0); - cleanup_sockets(); - return status; - } - else { - // Notify agent factory of success and send data to agent process - const std::string connection_successful{"connection_successful"}; - send(agent_conn_socket, connection_successful.c_str(), connection_successful.length() + 1, 0); - } - - status = sendEnvironmentVarStrToSocket(SP_RE_CACHE_SALT, - irods::get_server_property(irods::KW_CFG_RE_CACHE_SALT).c_str(), - tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_RE_CACHE_SALT to agent"); - } - - status = sendEnvironmentVarIntToSocket(SP_CONNECT_CNT, startupPack->connectCnt, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_CONNECT_CNT to agent"); - } - - status = sendEnvironmentVarStrToSocket(SP_PROXY_RODS_ZONE, startupPack->proxyRodsZone, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_PROXY_RODS_ZONE to agent"); - } - - status = sendEnvironmentVarIntToSocket(SP_NEW_SOCK, newSock, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_NEW_SOCK to agent"); - } - - status = sendEnvironmentVarIntToSocket(SP_PROTOCOL, startupPack->irodsProt, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_PROTOCOL to agent"); - } - - status = sendEnvironmentVarIntToSocket(SP_RECONN_FLAG, startupPack->reconnFlag, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_RECONN_FLAG to agent"); - } - - status = sendEnvironmentVarStrToSocket(SP_PROXY_USER, startupPack->proxyUser, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_PROXY_USER to agent"); - } - - status = sendEnvironmentVarStrToSocket(SP_CLIENT_USER, startupPack->clientUser, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_CLIENT_USER to agent"); - } - - status = sendEnvironmentVarStrToSocket(SP_CLIENT_RODS_ZONE, startupPack->clientRodsZone, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_CLIENT_RODS_ZONE to agent"); - } - - status = sendEnvironmentVarStrToSocket(SP_REL_VERSION, startupPack->relVersion, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_REL_VERSION to agent"); - } - - status = sendEnvironmentVarStrToSocket(SP_API_VERSION, startupPack->apiVersion, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_API_VERSION to agent"); - } - - // If the client-server negotiation request is in the option variable, set that env var - // and strip it out - std::string opt_str(startupPack->option); - std::size_t pos = opt_str.find(REQ_SVR_NEG); - if (std::string::npos != pos) { - std::string trunc_str = opt_str.substr(0, pos); - - status = sendEnvironmentVarStrToSocket(SP_OPTION, trunc_str.c_str(), tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_OPTION to agent"); - } - - status = sendEnvironmentVarStrToSocket(irods::RODS_CS_NEG, REQ_SVR_NEG, tmp_socket); - if (status < 0) { - log_server::error("Failed to send irods::RODS_CS_NEG to agent"); - } - } - else { - status = sendEnvironmentVarStrToSocket(SP_OPTION, startupPack->option, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SP_OPTION to agent"); - } - } - - status = sendEnvironmentVarIntToSocket(SERVER_BOOT_TIME, ServerBootTime, tmp_socket); - if (status < 0) { - log_server::error("Failed to send SERVER_BOOT_TIME to agent"); - } - - status = send(tmp_socket, "end_of_vars", 12, 0); - if (status <= 0) { - log_server::error("Failed to send \"end_of_vars;\" to agent"); - } - - status = recv(tmp_socket, &in_buf, sizeof(in_buf), 0); - if (status < 0) { - log_server::error("Error in recv acknowledgement from agent factory process, errno = [{}]: {}", - errno, - strerror(errno)); - cleanup_sockets(); - return SYS_SOCK_READ_ERR; - } - - if (std::strcmp(in_buf, "OK") != 0) { - log_server::error("Bad acknowledgement from agent factory process, message = [{}]", in_buf); - cleanup_sockets(); - return SYS_SOCK_READ_ERR; - } - - sendSocketOverSocket(tmp_socket, newSock); - status = recv(tmp_socket, &in_buf, sizeof(in_buf), 0); - if (status < 0) { - log_server::error("Error in recv child_pid from agent factory process, errno = [{}]: {}", - errno, - strerror(errno)); - cleanup_sockets(); - return SYS_SOCK_READ_ERR; - } - - cleanup_sockets(); - - return std::atoi(in_buf); -} - -int getAgentProcCnt() -{ - boost::unique_lock con_agent_lock(ConnectedAgentMutex); - - agentProc_t* tmpAgentProc = ConnectedAgentHead; - int count = 0; - - while (tmpAgentProc) { - ++count; - tmpAgentProc = tmpAgentProc->next; - } - - con_agent_lock.unlock(); - - return count; -} - -int getAgentProcPIDs(std::vector& _pids) -{ - boost::unique_lock con_agent_lock(ConnectedAgentMutex); - - agentProc_t* tmp_proc = ConnectedAgentHead; - int count = 0; - - while (tmp_proc) { - count++; - _pids.push_back(tmp_proc->pid); - tmp_proc = tmp_proc->next; - } - - con_agent_lock.unlock(); - - return count; -} // getAgentProcPIDs - -int chkAgentProcCnt() -{ - int maximum_connections = NO_MAX_CONNECTION_LIMIT; - - try { - if (irods::server_property_exists("maximum_connections")) { - maximum_connections = irods::get_server_property("maximum_connections"); - int count = getAgentProcCnt(); - - if (count >= maximum_connections) { - chkConnectedAgentProcQueue(); - count = getAgentProcCnt(); - - if (count >= maximum_connections) { - return SYS_MAX_CONNECT_COUNT_EXCEEDED; - } - } - } - - } - catch (const nlohmann::json::exception& e) { - log_server::error("%s failed with message [{}]", e.what()); - return SYS_INTERNAL_ERR; - } - - return 0; -} - -int chkConnectedAgentProcQueue() -{ - boost::unique_lock con_agent_lock(ConnectedAgentMutex); - agentProc_t* tmpAgentProc = ConnectedAgentHead; - agentProc_t* prevAgentProc{}; - - while (tmpAgentProc) { - char procPath[MAX_NAME_LEN]; - - std::snprintf(procPath, MAX_NAME_LEN, "%s/%-d", ProcLogDir, tmpAgentProc->pid); - - if (!boost::filesystem::exists(procPath)) { - // The agent proc is gone. - auto* unmatchedAgentProc = tmpAgentProc; - log_server::debug("Agent process {} in Connected queue but not in ProcLogDir", tmpAgentProc->pid); - - if (!prevAgentProc) { - ConnectedAgentHead = tmpAgentProc->next; - } - else { - prevAgentProc->next = tmpAgentProc->next; - } - - tmpAgentProc = tmpAgentProc->next; - std::free(unmatchedAgentProc); - } - else { - prevAgentProc = tmpAgentProc; - tmpAgentProc = tmpAgentProc->next; - } - } - - con_agent_lock.unlock(); - - return 0; -} - -int initServer(rsComm_t* svrComm) -{ - int status = initServerInfo(0, svrComm); - if (status < 0) { - log_server::info("initServer: initServerInfo error, status = {}", status); - return status; - } - - resc_mgr.print_local_resources(); - - printZoneInfo(); - - rodsServerHost_t* rodsServerHost{}; - status = getRcatHost(PRIMARY_RCAT, nullptr, &rodsServerHost); - - if (status < 0 || !rodsServerHost) { - return status; - } - - std::string svc_role; - irods::error ret = get_catalog_service_role(svc_role); - if (!ret.ok()) { - irods::log(PASS(ret)); - return ret.code(); - } - - - if (LOCAL_HOST == rodsServerHost->localFlag) { - if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == svc_role) { - disconnectRcat(); - } - } - else if (rodsServerHost->conn) { - rcDisconnect(rodsServerHost->conn); - rodsServerHost->conn = nullptr; - } - - if (irods::KW_CFG_SERVICE_ROLE_PROVIDER == svc_role) { - purgeLockFileDir(0); - } - - return status; -} - -// record the server process number and other information into -// a well-known file. If svrComm is Null and this has created a file -// before, just unlink the file. -int recordServerProcess(rsComm_t* svrComm) -{ - static char filePath[100] = ""; - - if (!svrComm) { - if (filePath[0] != '\0') { - unlink(filePath); - } - - return 0; - } - - rodsEnv* myEnv = &svrComm->myEnv; - - // Use /usr/tmp if it exists, /tmp otherwise. - char* tmp; - if (DIR* dirp = opendir("/usr/tmp"); dirp) { - tmp = "/usr/tmp"; - closedir(dirp); - } - else { - tmp = "/tmp"; - } - - char stateFile[] = "irodsServer"; - std::sprintf(filePath, "%s/%s.%d", tmp, stateFile, myEnv->rodsPort); - unlink(filePath); - - char cwd[1000]; - - if (char* cp = getcwd(cwd, 1000); cp) { - auto* fd = std::fopen(filePath, "w"); - if (fd) { - std::fprintf(fd, "%d %s\n", getpid(), cwd); - std::fclose(fd); - - if (const int ec = chmod(filePath, 0664); ec != 0) { - log_server::error("chmod failed in recordServerProcess on [{}] with error code {}", filePath, ec); - } - } - } - - return 0; -} - -int initServerMain(rsComm_t *svrComm, - const bool enable_test_mode = false, - const bool write_to_stdout = false) -{ - std::memset(svrComm, 0, sizeof(*svrComm)); - int status = getRodsEnv(&svrComm->myEnv); - if (status < 0) { - log_server::error("{}: getRodsEnv error. status = {}", __func__, status); - return status; - } - initAndClearProcLog(); - - setRsCommFromRodsEnv(svrComm); - - // Load server API table so that API plugins which are needed to stand up the server are - // available for use. - irods::api_entry_table& RsApiTable = irods::get_server_api_table(); - irods::pack_entry_table& ApiPackTable = irods::get_pack_table(); - if (const auto err = irods::init_api_table(RsApiTable, ApiPackTable, false); !err.ok()) { - irods::log(PASS(err)); - return err.code(); - } - - // If this is a catalog service consumer, the client API table should be loaded so that - // client calls can be made to the catalog service provider as part of the server - // initialization process. - irods::api_entry_table& RcApiTable = irods::get_client_api_table(); - if (const auto err = irods::init_api_table(RcApiTable, ApiPackTable, false); !err.ok()) { - irods::log(PASS(err)); - return err.code(); - } - - status = initServer(svrComm); - if (status < 0) { - log_server::error("{}: initServer error. status = {}", __func__, status); - exit(1); - } - - int zone_port; - try { - zone_port = irods::get_server_property(irods::KW_CFG_ZONE_PORT); - } - catch (const irods::exception& e) { - irods::log(irods::error(e)); - return e.code(); - } - - svrComm->sock = sockOpenForInConn(svrComm, &zone_port, nullptr, SOCK_STREAM); - if (svrComm->sock < 0) { - log_server::error("{}: sockOpenForInConn error. status = {}", __func__, svrComm->sock); - return svrComm->sock; - } - - if (listen(svrComm->sock, MAX_LISTEN_QUE) < 0) { - log_server::error("{}: listen failed, errno: {}", __func__, errno); - return SYS_SOCK_LISTEN_ERR; - } - - log_server::info("rodsServer Release version {} - API Version {} is up", RODS_REL_VERSION, RODS_API_VERSION); - - // Record port, PID, and CWD into a well-known file. - recordServerProcess(svrComm); - - // Setup the delay server CRON task. - // The delay server will launch just before we enter the server's main loop. - ix::cron::cron_builder delay_server; - const auto migrate_delay_server_sleep_time = - get_advanced_setting(irods::KW_CFG_MIGRATE_DELAY_SERVER_SLEEP_TIME_IN_SECONDS, 5); - delay_server.interval(migrate_delay_server_sleep_time).task([enable_test_mode, write_to_stdout] { - migrate_delay_server(enable_test_mode, write_to_stdout); - }); - ix::cron::cron::instance().add_task(delay_server.build()); - - return 0; -} - -// Add incoming connection request to the bottom of the link list. -int addConnReqToQueue(rsComm_t* rsComm, int sock) -{ - boost::unique_lock read_req_lock(ReadReqCondMutex); - auto* myConnReq = (agentProc_t*) std::calloc(1, sizeof(agentProc_t)); - - myConnReq->sock = sock; - myConnReq->remoteAddr = rsComm->remoteAddr; - queueAgentProc(myConnReq, &ConnReqHead, BOTTOM_POS); - - ReadReqCond.notify_all(); // NOTE: Check all vs one. - read_req_lock.unlock(); - - return 0; -} - -agentProc_t* getConnReqFromQueue() -{ - agentProc_t* myConnReq{}; - - while (true) { - const auto state = irods::server_state::get_state(); - - if (irods::server_state::server_state::stopped == state || - irods::server_state::server_state::exited == state || - myConnReq) - { - break; - } - - boost::unique_lock read_req_lock(ReadReqCondMutex); - if (ConnReqHead) { - myConnReq = ConnReqHead; - ConnReqHead = ConnReqHead->next; - read_req_lock.unlock(); - break; - } - - ReadReqCond.wait(read_req_lock); - if (!ConnReqHead) { - read_req_lock.unlock(); - continue; - } - - myConnReq = ConnReqHead; - ConnReqHead = ConnReqHead->next; - read_req_lock.unlock(); - break; - } - - return myConnReq; -} - -int process_bad_request() -{ - boost::unique_lock bad_req_lock(BadReqMutex); - - agentProc_t* tmpConnReq = BadReqHead; - agentProc_t* nextConnReq{}; - - // Just free them for now. - - while (tmpConnReq) { - nextConnReq = tmpConnReq->next; - std::free(tmpConnReq); - tmpConnReq = nextConnReq; - } - - BadReqHead = nullptr; - bad_req_lock.unlock(); - - return 0; -} - -void task_spawn_manager() -{ - unsigned int agentQueChkTime = 0; - - while (true) { - const auto state = irods::server_state::get_state(); - - if (irods::server_state::server_state::stopped == state || irods::server_state::server_state::exited == state) { - break; - } - - boost::unique_lock spwn_req_lock(SpawnReqCondMutex); - SpawnReqCond.wait(spwn_req_lock); - - while (SpawnReqHead) { - agentProc_t* mySpawnReq = SpawnReqHead; - SpawnReqHead = SpawnReqHead->next; - - spwn_req_lock.unlock(); - auto status = spawnAgent(mySpawnReq, &ConnectedAgentHead); - close(mySpawnReq->sock); - - if (status < 0) { - log_server::info( - "spawnAgent error for puser=[{}] and cuser=[{}] from [{}], stat=[{}]", - mySpawnReq->startupPack.proxyUser, - mySpawnReq->startupPack.clientUser, - inet_ntoa(mySpawnReq->remoteAddr.sin_addr), - status); - std::free(mySpawnReq); - } - else { - log_server::debug( - "Agent process [{}] started for puser=[{}] and cuser=[{}] from [{}]", - mySpawnReq->pid, - mySpawnReq->startupPack.proxyUser, - mySpawnReq->startupPack.clientUser, - inet_ntoa(mySpawnReq->remoteAddr.sin_addr)); - } - - spwn_req_lock.lock(); - } - - spwn_req_lock.unlock(); - - if (unsigned int curTime = std::time(nullptr); curTime > agentQueChkTime + AGENT_QUE_CHK_INT) { - agentQueChkTime = curTime; - process_bad_request(); - } - } -} - -void readWorkerTask() -{ - // Artificially create a conn object in order to create a network object. - // This is gratuitous but necessary to maintain the consistent interface. - RcComm tmp_comm{}; - - irods::network_object_ptr net_obj; - irods::error ret = irods::network_factory(&tmp_comm, net_obj); - - if (!ret.ok() || !net_obj.get()) { - irods::log(PASS(ret)); - return; - } - - while (true) { - const auto state = irods::server_state::get_state(); - - if (irods::server_state::server_state::stopped == state || - irods::server_state::server_state::exited == state) - { - break; - } - - agentProc_t* myConnReq = getConnReqFromQueue(); - if (!myConnReq) { - // Someone else took care of it. - continue; - } - - int newSock = myConnReq->sock; - - // Repave the socket handle with the new socket for this connection. - net_obj->socket_handle(newSock); - startupPack_t* startupPack = nullptr; - struct timeval tv; - tv.tv_sec = READ_STARTUP_PACK_TOUT_SEC; - tv.tv_usec = 0; - irods::error ret = readStartupPack(net_obj, &startupPack, &tv); - - if (!ret.ok()) { - log_server::error("readWorkerTask - readStartupPack failed. {}", ret.code()); - sendVersion(net_obj, ret.code(), 0, nullptr, 0); - boost::unique_lock bad_req_lock(BadReqMutex); - queueAgentProc(myConnReq, &BadReqHead, TOP_POS); - bad_req_lock.unlock(); - mySockClose(newSock); - } - else if (std::strcmp(startupPack->option, RODS_HEARTBEAT_T) == 0) { - const char* heartbeat = RODS_HEARTBEAT_T; - const int heartbeat_length = std::strlen(heartbeat); - int bytes_to_send = heartbeat_length; - - while (bytes_to_send) { - const int bytes_sent = send(newSock, &(heartbeat[heartbeat_length - bytes_to_send]), bytes_to_send, 0); - const int errsav = errno; - if (bytes_sent > 0) { - bytes_to_send -= bytes_sent; - } - else if (errsav != EINTR) { - log_server::error("Socket error encountered during heartbeat; socket returned {}", - strerror(errsav)); - break; - } - } - - mySockClose(newSock); - std::free(myConnReq); - std::free(startupPack); - } - else if (startupPack->connectCnt > MAX_SVR_SVR_CONNECT_CNT) { - sendVersion(net_obj, SYS_EXCEED_CONNECT_CNT, 0, nullptr, 0); - mySockClose(newSock); - std::free(myConnReq); - std::free(startupPack); - } - else { - if (startupPack->clientUser[0] != '\0') { - if (const auto status = chkAllowedUser(startupPack->clientUser, startupPack->clientRodsZone); status < 0) { - sendVersion(net_obj, status, 0, nullptr, 0); - mySockClose(newSock); - std::free(myConnReq); - continue; - } - } - - myConnReq->startupPack = *startupPack; - std::free(startupPack); - - boost::unique_lock spwn_req_lock(SpawnReqCondMutex); - - queueAgentProc(myConnReq, &SpawnReqHead, BOTTOM_POS); - - SpawnReqCond.notify_all(); // NOTE:: look into notify_one vs notify_all - } - } -} // readWorkerTask - -int procSingleConnReq(agentProc_t* connReq) -{ - if (!connReq) { - return USER__NULL_INPUT_ERR; - } - - int newSock = connReq->sock; - - // =-=-=-=-=-=-=- - // artificially create a conn object in order to - // create a network object. this is gratuitous - // but necessary to maintain the consistent interface. - rcComm_t tmp_comm; - std::memset(&tmp_comm, 0, sizeof(rcComm_t)); - - irods::network_object_ptr net_obj; - irods::error ret = irods::network_factory(&tmp_comm, net_obj); - if (!ret.ok()) { - irods::log(PASS(ret)); - return -1; - } - - net_obj->socket_handle(newSock); - - startupPack_t* startupPack; - ret = readStartupPack(net_obj, &startupPack, nullptr); - - if (!ret.ok()) { - log_server::info("readStartupPack error from {}, status = {}", - inet_ntoa(connReq->remoteAddr.sin_addr), - ret.code()); - sendVersion(net_obj, ret.code(), 0, nullptr, 0); - mySockClose(newSock); - return ret.code(); - } - - if (startupPack->connectCnt > MAX_SVR_SVR_CONNECT_CNT) { - sendVersion(net_obj, SYS_EXCEED_CONNECT_CNT, 0, nullptr, 0); - mySockClose(newSock); - return SYS_EXCEED_CONNECT_CNT; - } - - connReq->startupPack = *startupPack; - std::free(startupPack); - - int status = spawnAgent(connReq, &ConnectedAgentHead); - - close(newSock); - - if (status < 0) { - log_server::info("spawnAgent error for puser={} and cuser={} from {}, status = {}", - connReq->startupPack.proxyUser, - connReq->startupPack.clientUser, - inet_ntoa(connReq->remoteAddr.sin_addr), - status); - } - else { - log_server::debug("Agent process {} started for puser={} and cuser={} from {}", - connReq->pid, - connReq->startupPack.proxyUser, - connReq->startupPack.clientUser, - inet_ntoa(connReq->remoteAddr.sin_addr)); - } - - return status; -} diff --git a/server/re/include/irods/irods_re_plugin.hpp b/server/re/include/irods/irods_re_plugin.hpp index c54c171afd..33994e3ff3 100644 --- a/server/re/include/irods/irods_re_plugin.hpp +++ b/server/re/include/irods/irods_re_plugin.hpp @@ -7,25 +7,27 @@ #include "irods/irods_re_structs.hpp" #include "irods/irods_state_table.h" +#include +#include + +#include + +#include +#include #include #include -#include -#include -#include #include #include -#include #include - -#include -#include +#include +#include #ifdef IRODS_ENABLE_SYSLOG - #define IRODS_SERVER_ONLY(x) x - #include "irods/irods_logger.hpp" -namespace logger = irods::experimental::log; +# define IRODS_SERVER_ONLY(x) x +# include "irods/irods_logger.hpp" +using log_re = irods::experimental::log::rule_engine; #else - #define IRODS_SERVER_ONLY(x) +# define IRODS_SERVER_ONLY(x) #endif // IRODS_ENABLE_SYSLOG namespace irods { @@ -193,6 +195,34 @@ namespace irods { } + error setup_operation(T& _in) + { + try { + auto fcn = boost::any_cast>(operations_["setup"]); + return fcn(_in, instance_name_); + } + catch (const boost::bad_any_cast& e) { + return ERROR(INVALID_ANY_CAST, fmt::format("failed to extract setup operation from instance [{}]: {}", instance_name_, e.what())); + } + catch (const std::exception& e) { + return ERROR(PLUGIN_ERROR, fmt::format("failed to extract setup operation from instance [{}]: {}", instance_name_, e.what())); + } + } // setup_operation + + error teardown_operation(T& _in) + { + try { + auto fcn = boost::any_cast>(operations_["teardown"]); + return fcn(_in, instance_name_); + } + catch (const boost::bad_any_cast& e) { + return ERROR(INVALID_ANY_CAST, fmt::format("failed to extract teardown operation from instance [{}]: {}", instance_name_, e.what())); + } + catch (const std::exception& e) { + return ERROR(PLUGIN_ERROR, fmt::format("failed to extract teardown operation from instance [{}]: {}", instance_name_, e.what())); + } + } // teardown_operation + error start_operation(T& _in) { try { auto fcn = boost::any_cast>( operations_["start"] ); @@ -368,7 +398,7 @@ namespace irods { error err = load_plugin > (_re_ptr, _plugin_name, dir_, _inst_name, std::string("empty_context")); if (!err.ok()) { - irods::log( PASS( err ) ); + IRODS_SERVER_ONLY((log_re::error(PASS(err).user_result()))); return err; } @@ -395,7 +425,7 @@ namespace irods { std::for_each(begin(_re_packs), end(_re_packs), [this](re_pack_inp &_inp) { error err = this->init_rule_engine(_inp); if( !err.ok() ) { - irods::log( PASS( err ) ); + IRODS_SERVER_ONLY((log_re::error(PASS(err).user_result()))); } }); } @@ -418,6 +448,18 @@ namespace irods { return SUCCESS(); } + void call_setup_operations() { + std::for_each(begin(re_packs_), end(re_packs_), [](re_pack_inp &_inp) { + _inp.re_->setup_operation(_inp.re_ctx_); + }); + } + + void call_teardown_operations() { + std::for_each(begin(re_packs_), end(re_packs_), [](re_pack_inp &_inp) { + _inp.re_->teardown_operation(_inp.re_ctx_); + }); + } + void call_start_operations() { std::for_each(begin(re_packs_), end(re_packs_), [](re_pack_inp &_inp) { _inp.re_->start_operation(_inp.re_ctx_); @@ -459,10 +501,10 @@ namespace irods { e.code() == RE_PARSER_ERROR || e.code() == RULE_ENGINE_ERROR) { - logger::rule_engine::debug("Rule Engine Plugin returned [{}].", e.code()); + log_re::debug("Rule Engine Plugin returned [{}].", e.code()); } else { - logger::rule_engine::error("Rule Engine Plugin returned [{}].", e.code()); + log_re::error("Rule Engine Plugin returned [{}].", e.code()); } // clang-format on ) diff --git a/third_party/jsoncons/allocator_holder.hpp b/third_party/jsoncons/allocator_holder.hpp new file mode 100644 index 0000000000..9bb0141f5a --- /dev/null +++ b/third_party/jsoncons/allocator_holder.hpp @@ -0,0 +1,38 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_ALLOCATOR_HOLDER_HPP +#define JSONCONS_ALLOCATOR_HOLDER_HPP + +namespace jsoncons { + +template +class allocator_holder +{ +public: + using allocator_type = Allocator; +private: + allocator_type alloc_; +public: + allocator_holder() = default; + allocator_holder(const allocator_holder&) = default; + allocator_holder(allocator_holder&&) = default; + allocator_holder& operator=(const allocator_holder&) = default; + allocator_holder& operator=(allocator_holder&&) = default; + allocator_holder(const allocator_type& alloc) + : alloc_(alloc) + {} + ~allocator_holder() = default; + + allocator_type get_allocator() const + { + return alloc_; + } +}; + +} + +#endif diff --git a/third_party/jsoncons/allocator_set.hpp b/third_party/jsoncons/allocator_set.hpp new file mode 100644 index 0000000000..bc5d1269cb --- /dev/null +++ b/third_party/jsoncons/allocator_set.hpp @@ -0,0 +1,66 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_ALLOCATOR_STRATEGY_HPP +#define JSONCONS_ALLOCATOR_STRATEGY_HPP + +#include +#include + +namespace jsoncons { + +template +class allocator_set +{ + Allocator result_alloc_; + TempAllocator temp_alloc_; +public: + using allocator_type = Allocator; + using temp_allocator_type = TempAllocator; + + allocator_set(const Allocator& alloc=Allocator(), + const TempAllocator& temp_alloc=TempAllocator()) + : result_alloc_(alloc), temp_alloc_(temp_alloc) + { + } + + allocator_set(const allocator_set&) = default; + allocator_set(allocator_set&&) = default; + allocator_set& operator=(const allocator_set&) = delete; + allocator_set& operator=(allocator_set&&) = delete; + ~allocator_set() = default; + + Allocator get_allocator() const {return result_alloc_;} + TempAllocator get_temp_allocator() const {return temp_alloc_;} +}; + +inline +allocator_set,std::allocator> combine_allocators() +{ + return allocator_set,std::allocator>(std::allocator(), std::allocator()); +} + +template +allocator_set> combine_allocators(const Allocator& alloc) +{ + return allocator_set>(alloc, std::allocator()); +} + +template +allocator_set combine_allocators(const Allocator& alloc, const TempAllocator& temp_alloc) +{ + return allocator_set(alloc, temp_alloc); +} + +template +allocator_set,TempAllocator> temp_allocator_only(const TempAllocator& temp_alloc) +{ + return allocator_set,TempAllocator>(std::allocator(), temp_alloc); +} + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/basic_json.hpp b/third_party/jsoncons/basic_json.hpp new file mode 100644 index 0000000000..dce51d8a0a --- /dev/null +++ b/third_party/jsoncons/basic_json.hpp @@ -0,0 +1,5503 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BASIC_JSON_HPP +#define JSONCONS_BASIC_JSON_HPP + +#include // std::numeric_limits +#include +#include +#include +#include +#include +#include // std::allocator +#include +#include // std::memcpy +#include // std::swap +#include // std::initializer_list +#include // std::move +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_POLYMORPHIC_ALLOCATOR) +#include // std::poymorphic_allocator +#endif + +namespace jsoncons { + + namespace extension_traits { + + template + using + container_array_iterator_type_t = decltype(Container::array_iterator_type); + template + using + container_const_array_iterator_type_t = decltype(Container::const_array_iterator_type); + template + using + container_object_iterator_type_t = decltype(Container::object_iterator_type); + template + using + container_const_object_iterator_type_t = decltype(Container::const_object_iterator_type); + + namespace detail { + + template + using + basic_json_t = basic_json; + + } // namespace detail + + template + struct is_basic_json : std::false_type {}; + + template + struct is_basic_json::type>::value>::type + > : std::true_type {}; + + } // namespace extension_traits + + namespace detail { + + template + class random_access_iterator_wrapper + { + }; + + template + class random_access_iterator_wrapper::iterator_category, + std::random_access_iterator_tag>::value>::type> + { + Iterator it_; + bool has_value_; + + template + friend class random_access_iterator_wrapper; + public: + using iterator_category = std::random_access_iterator_tag; + + using value_type = typename std::iterator_traits::value_type; + using difference_type = typename std::iterator_traits::difference_type; + using pointer = typename std::iterator_traits::pointer; + using reference = typename std::iterator_traits::reference; + + random_access_iterator_wrapper() : it_(), has_value_(false) + { + } + + explicit random_access_iterator_wrapper(Iterator ptr) : it_(ptr), has_value_(true) + { + } + + random_access_iterator_wrapper(const random_access_iterator_wrapper&) = default; + random_access_iterator_wrapper(random_access_iterator_wrapper&&) = default; + random_access_iterator_wrapper& operator=(const random_access_iterator_wrapper&) = default; + random_access_iterator_wrapper& operator=(random_access_iterator_wrapper&&) = default; + + template ::value && std::is_convertible::value>::type> + random_access_iterator_wrapper(const random_access_iterator_wrapper& other) + : it_(other.it_), has_value_(other.has_value_) + { + } + + operator Iterator() const + { + return it_; + } + + reference operator*() const + { + return *it_; + } + + pointer operator->() const + { + return &(*it_); + } + + random_access_iterator_wrapper& operator++() + { + ++it_; + return *this; + } + + random_access_iterator_wrapper operator++(int) + { + random_access_iterator_wrapper temp = *this; + ++*this; + return temp; + } + + random_access_iterator_wrapper& operator--() + { + --it_; + return *this; + } + + random_access_iterator_wrapper operator--(int) + { + random_access_iterator_wrapper temp = *this; + --*this; + return temp; + } + + random_access_iterator_wrapper& operator+=(const difference_type offset) + { + it_ += offset; + return *this; + } + + random_access_iterator_wrapper operator+(const difference_type offset) const + { + random_access_iterator_wrapper temp = *this; + return temp += offset; + } + + random_access_iterator_wrapper& operator-=(const difference_type offset) + { + return *this += -offset; + } + + random_access_iterator_wrapper operator-(const difference_type offset) const + { + random_access_iterator_wrapper temp = *this; + return temp -= offset; + } + + difference_type operator-(const random_access_iterator_wrapper& rhs) const noexcept + { + return it_ - rhs.it_; + } + + reference operator[](const difference_type offset) const noexcept + { + return *(*this + offset); + } + + bool operator==(const random_access_iterator_wrapper& rhs) const noexcept + { + if (!has_value_ || !rhs.has_value_) + { + return has_value_ == rhs.has_value_ ? true : false; + } + else + { + return it_ == rhs.it_; + } + } + + bool operator!=(const random_access_iterator_wrapper& rhs) const noexcept + { + return !(*this == rhs); + } + + bool operator<(const random_access_iterator_wrapper& rhs) const noexcept + { + if (!has_value_ || !rhs.has_value_) + { + return has_value_ == rhs.has_value_ ? false :(has_value_ ? false : true); + } + else + { + return it_ < rhs.it_; + } + } + + bool operator>(const random_access_iterator_wrapper& rhs) const noexcept + { + return rhs < *this; + } + + bool operator<=(const random_access_iterator_wrapper& rhs) const noexcept + { + return !(rhs < *this); + } + + bool operator>=(const random_access_iterator_wrapper& rhs) const noexcept + { + return !(*this < rhs); + } + + inline + friend random_access_iterator_wrapper operator+( + difference_type offset, random_access_iterator_wrapper next) + { + return next += offset; + } + + bool has_value() const + { + return has_value_; + } + }; + } // namespace detail + + struct sorted_policy + { + template + using object = sorted_json_object; + + template + using array = json_array; + + template + using member_key = std::basic_string; + }; + + struct order_preserving_policy + { + template + using object = order_preserving_json_object; + + template + using array = json_array; + + template + using member_key = std::basic_string; + }; + + template + struct object_iterator_typedefs + { + }; + + template + struct object_iterator_typedefs::value || + !extension_traits::is_detected::value>::type> + { + using object_iterator_type = jsoncons::detail::random_access_iterator_wrapper::iterator>; + using const_object_iterator_type = jsoncons::detail::random_access_iterator_wrapper::const_iterator>; + }; + + template + struct object_iterator_typedefs::value && + extension_traits::is_detected::value>::type> + { + using object_iterator_type = jsoncons::detail::random_access_iterator_wrapper>; + using const_object_iterator_type = jsoncons::detail::random_access_iterator_wrapper>; + }; + + template + struct array_iterator_typedefs + { + }; + + template + struct array_iterator_typedefs::value || + !extension_traits::is_detected::value>::type> + { + using array_iterator_type = typename Policy::template array::iterator; + using const_array_iterator_type = typename Policy::template array::const_iterator; + }; + + template + struct array_iterator_typedefs::value && + extension_traits::is_detected::value>::type> + { + using array_iterator_type = typename Policy::template array_iterator_type; + using const_array_iterator_type = typename Policy::template const_array_iterator_type; + }; + + template + class range + { + public: + using iterator = IteratorT; + using const_iterator = ConstIteratorT; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + private: + iterator first_; + iterator last_; + public: + range(const IteratorT& first, const IteratorT& last) + : first_(first), last_(last) + { + } + + iterator begin() const noexcept + { + return first_; + } + iterator end() const noexcept + { + return last_; + } + const_iterator cbegin() const noexcept + { + return first_; + } + const_iterator cend() const noexcept + { + return last_; + } + reverse_iterator rbegin() const noexcept + { + return reverse_iterator(last_); + } + reverse_iterator rend() const noexcept + { + return reverse_iterator(first_); + } + const_reverse_iterator crbegin() const noexcept + { + return reverse_iterator(last_); + } + const_reverse_iterator crend() const noexcept + { + return reverse_iterator(first_); + } + }; + + // is_proxy_of + + template + struct is_proxy_of : std::false_type {}; + + template + struct is_proxy_of::value>::type + > : std::true_type {}; + + + // is_proxy + + template + struct is_proxy : std::false_type {}; + + template + struct is_proxy::value>::type + > : std::true_type {}; + + template + class basic_json + { + public: + static_assert(extension_traits::is_stateless::value || extension_traits::is_propagating_allocator::value, + "Regular stateful allocators must be wrapped with std::scoped_allocator_adaptor"); + + using allocator_type = Allocator; + + using policy_type = Policy; + + using char_type = CharT; + using char_traits_type = std::char_traits; + using string_view_type = jsoncons::basic_string_view; + + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + using string_type = std::basic_string; + + using key_type = typename policy_type::template member_key; + + + using reference = basic_json&; + using const_reference = const basic_json&; + using pointer = basic_json*; + using const_pointer = const basic_json*; + + using key_value_type = key_value; + + using array = typename policy_type::template array; + + using key_value_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + using object = typename policy_type::template object; + + using object_iterator = typename object_iterator_typedefs::object_iterator_type; + using const_object_iterator = typename object_iterator_typedefs::const_object_iterator_type; + using array_iterator = typename array_iterator_typedefs::array_iterator_type; + using const_array_iterator = typename array_iterator_typedefs::const_array_iterator_type; + + using object_range_type = range; + using const_object_range_type = range; + using array_range_type = range; + using const_array_range_type = range; + + private: + + static constexpr uint8_t major_type_shift = 0x04; + static constexpr uint8_t additional_information_mask = (1U << 4) - 1; + + public: + class common_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + }; + + class null_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + + null_storage(semantic_tag tag = semantic_tag::none) + : storage_kind_(static_cast(json_storage_kind::null)), short_str_length_(0), tag_(tag) + { + } + }; + + class empty_object_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + + empty_object_storage(semantic_tag tag) + : storage_kind_(static_cast(json_storage_kind::empty_object)), short_str_length_(0), tag_(tag) + { + } + }; + + class bool_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + bool val_; + public: + bool_storage(bool val, semantic_tag tag) + : storage_kind_(static_cast(json_storage_kind::boolean)), short_str_length_(0), tag_(tag), + val_(val) + { + } + + bool value() const + { + return val_; + } + + }; + + class int64_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + int64_t val_; + public: + int64_storage(int64_t val, + semantic_tag tag = semantic_tag::none) + : storage_kind_(static_cast(json_storage_kind::int64)), short_str_length_(0), tag_(tag), + val_(val) + { + } + + int64_t value() const + { + return val_; + } + }; + + class uint64_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + uint64_t val_; + public: + uint64_storage(uint64_t val, + semantic_tag tag = semantic_tag::none) + : storage_kind_(static_cast(json_storage_kind::uint64)), short_str_length_(0), tag_(tag), + val_(val) + { + } + + uint64_t value() const + { + return val_; + } + }; + + class half_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + uint16_t val_; + public: + half_storage(uint16_t val, semantic_tag tag = semantic_tag::none) + : storage_kind_(static_cast(json_storage_kind::half_float)), short_str_length_(0), tag_(tag), + val_(val) + { + } + + uint16_t value() const + { + return val_; + } + }; + + class double_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + double val_; + public: + double_storage(double val, + semantic_tag tag = semantic_tag::none) + : storage_kind_(static_cast(json_storage_kind::float64)), short_str_length_(0), tag_(tag), + val_(val) + { + } + + double value() const + { + return val_; + } + }; + + class short_string_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + static constexpr size_t capacity = (2*sizeof(uint64_t) - 2*sizeof(uint8_t))/sizeof(char_type); + char_type data_[capacity]; + public: + static constexpr size_t max_length = capacity - 1; + + short_string_storage(semantic_tag tag, const char_type* p, uint8_t length) + : storage_kind_(static_cast(json_storage_kind::short_str)), short_str_length_(length), tag_(tag) + { + JSONCONS_ASSERT(length <= max_length); + std::memcpy(data_,p,length*sizeof(char_type)); + data_[length] = 0; + } + + short_string_storage(const short_string_storage& other) + : storage_kind_(other.storage_kind_), short_str_length_(other.short_str_length_), tag_(other.tag_) + { + std::memcpy(data_,other.data_,other.short_str_length_*sizeof(char_type)); + data_[short_str_length_] = 0; + } + + short_string_storage& operator=(const short_string_storage& other) = delete; + + uint8_t length() const + { + return short_str_length_; + } + + const char_type* data() const + { + return data_; + } + + const char_type* c_str() const + { + return data_; + } + }; + + // long_string_storage + class long_string_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + using heap_string_factory_type = jsoncons::detail::heap_string_factory; + using pointer = typename heap_string_factory_type::pointer; + + pointer ptr_; + public: + + long_string_storage(semantic_tag tag, const char_type* data, std::size_t length, const Allocator& alloc) + : storage_kind_(static_cast(json_storage_kind::long_str)), short_str_length_(0), tag_(tag) + { + ptr_ = heap_string_factory_type::create(data, length, null_type(), alloc); + } + long_string_storage(const long_string_storage& other) + : storage_kind_(static_cast(json_storage_kind::long_str)), short_str_length_(0), tag_(other.tag_) + { + ptr_ = heap_string_factory_type::create(other.data(), other.length(), null_type(), other.get_allocator()); + } + + long_string_storage(const long_string_storage& other, const Allocator& alloc) + : storage_kind_(static_cast(json_storage_kind::long_str)), short_str_length_(0), tag_(other.tag_) + { + ptr_ = heap_string_factory_type::create(other.data(), other.length(), null_type(), alloc); + } + + long_string_storage(long_string_storage&& other) noexcept + : storage_kind_(static_cast(json_storage_kind::long_str)), short_str_length_(0), tag_(other.tag_) + { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + other.tag_ = semantic_tag::none; + other.storage_kind_ = static_cast(json_storage_kind::null); + } + + long_string_storage(long_string_storage&& other, const Allocator& alloc) + : storage_kind_(static_cast(json_storage_kind::long_str)), short_str_length_(0), tag_(other.tag_) + { + if (other.get_allocator() == alloc) + { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + other.tag_ = semantic_tag::none; + other.storage_kind_ = static_cast(json_storage_kind::null); + } + else + { + ptr_ = heap_string_factory_type::create(other.data(), other.length(), null_type(), alloc); + } + } + + void assign(const long_string_storage& other) + { + assign(std::integral_constant::propagate_on_container_copy_assignment::value>(), other); + } + + void assign(std::true_type, const long_string_storage& other) + { + tag_ = other.tag_; + heap_string_factory_type::destroy(ptr_); + ptr_ = heap_string_factory_type::create(other.data(), other.length(), null_type(), other.get_allocator()); + } + + void assign(std::false_type, const long_string_storage& other) + { + auto alloc = get_allocator(); + tag_ = other.tag_; + heap_string_factory_type::destroy(ptr_); + ptr_ = heap_string_factory_type::create(other.data(), other.length(), null_type(), alloc); + } + + void assign(long_string_storage&& other) + { + swap(other); + } + + ~long_string_storage() noexcept + { + heap_string_factory_type::destroy(ptr_); + } + + void swap(long_string_storage& other) + { + using std::swap; + swap(ptr_, other.ptr_); + swap(tag_, other.tag_); + } + + semantic_tag tag() const + { + return tag_; + } + + const char_type* data() const + { + return ptr_->data(); + } + + const char_type* c_str() const + { + return ptr_->c_str(); + } + + std::size_t length() const + { + return ptr_->length(); + } + + allocator_type get_allocator() const + { + return ptr_->get_allocator(); + } + }; + + // byte_string_storage + class byte_string_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + using heap_string_factory_type = jsoncons::detail::heap_string_factory; + using pointer = typename heap_string_factory_type::pointer; + + pointer ptr_; + public: + + byte_string_storage(semantic_tag tag, const uint8_t* data, std::size_t length, uint64_t ext_tag, const Allocator& alloc) + : storage_kind_(static_cast(json_storage_kind::byte_str)), short_str_length_(0), tag_(tag) + { + ptr_ = heap_string_factory_type::create(data, length, ext_tag, alloc); + } + + byte_string_storage(const byte_string_storage& other) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_) + { + ptr_ = heap_string_factory_type::create(other.data(), other.length(), other.ext_tag(), other.get_allocator()); + } + + byte_string_storage(const byte_string_storage& other, const Allocator& alloc) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_) + { + ptr_ = heap_string_factory_type::create(other.data(), other.length(), other.ext_tag(), alloc); + } + + byte_string_storage(byte_string_storage&& other) noexcept + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_) + { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + other.tag_ = semantic_tag::none; + other.storage_kind_ = static_cast(json_storage_kind::null); + } + + byte_string_storage(byte_string_storage&& other, const Allocator& alloc) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_) + { + if (other.get_allocator() == alloc) + { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + other.tag_ = semantic_tag::none; + other.storage_kind_ = static_cast(json_storage_kind::null); + } + else + { + ptr_ = heap_string_factory_type::create(other.data(), other.length(), other.ext_tag(), alloc); + } + } + + void assign(const byte_string_storage& other) + { + assign(std::integral_constant::propagate_on_container_copy_assignment::value>(), other); + } + + void assign(std::true_type, const byte_string_storage& other) + { + tag_ = other.tag_; + heap_string_factory_type::destroy(ptr_); + ptr_ = heap_string_factory_type::create(other.data(), other.length(), null_type(), other.get_allocator()); + } + + void assign(std::false_type, const byte_string_storage& other) + { + auto alloc = get_allocator(); + tag_ = other.tag_; + heap_string_factory_type::destroy(ptr_); + ptr_ = heap_string_factory_type::create(other.data(), other.length(), other.ext_tag(), alloc); + } + + void assign(byte_string_storage&& other) + { + swap(other); + } + + ~byte_string_storage() noexcept + { + heap_string_factory_type::destroy(ptr_); + } + + void swap(byte_string_storage& other) + { + using std::swap; + swap(ptr_, other.ptr_); + swap(tag_, other.tag_); + } + + semantic_tag tag() const + { + return tag_; + } + + const uint8_t* data() const + { + return ptr_->data(); + } + + std::size_t length() const + { + return ptr_->length(); + } + + uint64_t ext_tag() const + { + return ptr_->extra(); + } + + allocator_type get_allocator() const + { + return ptr_->get_allocator(); + } + }; +#if defined(__GNUC__) && JSONCONS_GCC_AVAILABLE(12,0,0) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + + // array_storage + class array_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + using array_allocator = typename std::allocator_traits:: template rebind_alloc; + using pointer = typename std::allocator_traits::pointer; + + pointer ptr_; + + template + void create(array_allocator alloc, Args&& ... args) + { + ptr_ = std::allocator_traits::allocate(alloc, 1); + JSONCONS_TRY + { + std::allocator_traits::construct(alloc, extension_traits::to_plain_pointer(ptr_), + std::forward(args)...); + } + JSONCONS_CATCH(...) + { + std::allocator_traits::deallocate(alloc, ptr_,1); + JSONCONS_RETHROW; + } + } + + void destroy() noexcept + { + array_allocator alloc(ptr_->get_allocator()); + std::allocator_traits::destroy(alloc, extension_traits::to_plain_pointer(ptr_)); + std::allocator_traits::deallocate(alloc, ptr_,1); + } + public: + array_storage(const array& val, semantic_tag tag) + : storage_kind_(static_cast(json_storage_kind::array)), short_str_length_(0), tag_(tag), ptr_(nullptr) + { + create(val.get_allocator(), val); + } + + array_storage(array&& val, semantic_tag tag) + : storage_kind_(static_cast(json_storage_kind::array)), short_str_length_(0), tag_(tag), ptr_(nullptr) + { + create(val.get_allocator(), std::move(val)); + } + + array_storage(const array_storage& other) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), ptr_(nullptr) + { + create(other.ptr_->get_allocator(), *(other.ptr_)); + } + + array_storage(array_storage&& other) noexcept + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), + ptr_(nullptr) + { + using std::swap; + swap(other.ptr_, ptr_); + + other.storage_kind_ = static_cast(json_storage_kind::null); + other.short_str_length_ = 0; + other.tag_ = semantic_tag::none; + } + + array_storage(const array_storage& other, const Allocator& alloc) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), ptr_(nullptr) + { + create(array_allocator(alloc), *(other.ptr_)); + } + + array_storage(array_storage&& other, const Allocator& alloc) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), ptr_(nullptr) + { + if (other.get_allocator() == alloc) + { + // Transfer resources + ptr_ = other.ptr_; + other.ptr_ = nullptr; + + other.storage_kind_ = static_cast(json_storage_kind::null); + other.tag_ = semantic_tag::none; + } + else + { + create(array_allocator(alloc), *(other.ptr_)); + } + } + + ~array_storage() noexcept + { + if (ptr_ != nullptr) + { + destroy(); + } + } + + void assign(const array_storage& other) + { + assign(std::integral_constant::propagate_on_container_copy_assignment::value>(), other); + } + + void assign(std::true_type, const array_storage& other) + { + tag_ = other.tag_; + destroy(); + create(array_allocator(other.get_allocator()), *(other.ptr_)); + } + + void assign(std::false_type, const array_storage& other) + { + auto alloc = get_allocator(); + tag_ = other.tag_; + destroy(); + create(array_allocator(alloc), *(other.ptr_)); + } + + void assign(array_storage&& other) + { + swap(other); + } + + void swap(array_storage& other) + { + using std::swap; + swap(ptr_, other.ptr_); + swap(tag_, other.tag_); + } + + semantic_tag tag() const + { + return tag_; + } + + allocator_type get_allocator() const + { + return ptr_->get_allocator(); + } + + array& value() + { + return *ptr_; + } + + const array& value() const + { + return *ptr_; + } + }; + + // object_storage + class object_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + using object_allocator = typename std::allocator_traits:: template rebind_alloc; + using pointer = typename std::allocator_traits::pointer; + + pointer ptr_; + + template + void create(object_allocator alloc, Args&& ... args) + { + ptr_ = std::allocator_traits::allocate(alloc, 1); + JSONCONS_TRY + { + std::allocator_traits::construct(alloc, extension_traits::to_plain_pointer(ptr_), std::forward(args)...); + } + JSONCONS_CATCH(...) + { + std::allocator_traits::deallocate(alloc, ptr_,1); + JSONCONS_RETHROW; + } + } + public: + object_storage(const object& val, semantic_tag tag) + : storage_kind_(static_cast(json_storage_kind::object)), short_str_length_(0), tag_(tag), ptr_(nullptr) + { + create(val.get_allocator(), val); + } + + object_storage(object&& val, semantic_tag tag) + : storage_kind_(static_cast(json_storage_kind::object)), short_str_length_(0), tag_(tag), ptr_(nullptr) + { + create(val.get_allocator(), std::move(val)); + } + + explicit object_storage(const object_storage& other) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), ptr_(nullptr) + { + create(other.ptr_->get_allocator(), *(other.ptr_)); + } + + object_storage(const object_storage& other, const Allocator& alloc) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), ptr_(nullptr) + { + create(object_allocator(alloc), *(other.ptr_)); + } + + explicit object_storage(object_storage&& other) noexcept + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), + ptr_(nullptr) + { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + other.storage_kind_ = static_cast(json_storage_kind::null); + other.short_str_length_ = 0; + other.tag_ = semantic_tag::none; + } + + object_storage(object_storage&& other, const Allocator& alloc) + : storage_kind_(other.storage_kind_), short_str_length_(0), tag_(other.tag_), ptr_(nullptr) + { + if (other.get_allocator() == alloc) + { + ptr_ = other.ptr_; + other.ptr_ = nullptr; + other.storage_kind_ = static_cast(json_storage_kind::null); + other.short_str_length_ = 0; + other.tag_ = semantic_tag::none; + } + else + { + create(object_allocator(alloc), *(other.ptr_)); + } + } + + ~object_storage() noexcept + { + if (ptr_ != nullptr) + { + destroy(); + } + } + + void assign(const object_storage& other) + { + assign(std::integral_constant::propagate_on_container_copy_assignment::value>(), other); + } + + void assign(std::true_type, const object_storage& other) + { + tag_ = other.tag_; + destroy(); + ptr_ = nullptr; + create(object_allocator(other.get_allocator()), *(other.ptr_)); + } + + void assign(std::false_type, const object_storage& other) + { + auto alloc = get_allocator(); + tag_ = other.tag_; + destroy(); + ptr_ = nullptr; + create(object_allocator(alloc), *(other.ptr_)); + } + + void assign(object_storage&& other) + { + swap(other); + } + + void swap(object_storage& other) + { + using std::swap; + swap(ptr_, other.ptr_); + swap(tag_, other.tag_); + } + + semantic_tag tag() const + { + return tag_; + } + + object& value() + { + JSONCONS_ASSERT(ptr_ != nullptr); + return *ptr_; + } + + const object& value() const + { + JSONCONS_ASSERT(ptr_ != nullptr); + return *ptr_; + } + + allocator_type get_allocator() const + { + JSONCONS_ASSERT(ptr_ != nullptr); + return ptr_->get_allocator(); + } + private: + + void destroy() noexcept + { + object_allocator alloc(ptr_->get_allocator()); + std::allocator_traits::destroy(alloc, extension_traits::to_plain_pointer(ptr_)); + std::allocator_traits::deallocate(alloc, ptr_,1); + } + }; +#if defined(__GNUC__) && JSONCONS_GCC_AVAILABLE(12,0,0) +# pragma GCC diagnostic pop +#endif + + private: + class json_const_pointer_storage final + { + public: + uint8_t storage_kind_:4; + uint8_t short_str_length_:4; + semantic_tag tag_; + private: + const basic_json* p_; + public: + json_const_pointer_storage(const basic_json* p) + : storage_kind_(static_cast(json_storage_kind::const_json_pointer)), short_str_length_(0), tag_(p->tag()), + p_(p) + { + } + + const basic_json* value() const + { + return p_; + } + }; + + template + class proxy + { + friend class basic_json; + + ParentType& parent_; + string_view_type key_; + + proxy() = delete; + + proxy(const proxy& other) = default; + proxy(proxy&& other) = default; + proxy& operator = (const proxy& other) = delete; + proxy& operator = (proxy&& other) = delete; + + proxy(ParentType& parent, const string_view_type& key) + : parent_(parent), key_(key) + { + } + + basic_json& evaluate_with_default() + { + basic_json& val = parent_.evaluate_with_default(); + auto it = val.find(key_); + if (it == val.object_range().end()) + { + auto r = val.try_emplace(key_, json_object_arg, semantic_tag::none); + return r.first->value(); + } + else + { + return it->value(); + } + } + + basic_json& evaluate(std::size_t index) + { + return evaluate().at(index); + } + + const basic_json& evaluate(std::size_t index) const + { + return evaluate().at(index); + } + + basic_json& evaluate(const string_view_type& index) + { + return evaluate().at(index); + } + + const basic_json& evaluate(const string_view_type& index) const + { + return evaluate().at(index); + } + public: + using proxied_type = basic_json; + using proxy_type = proxy; + + basic_json& evaluate() + { + return parent_.evaluate(key_); + } + + const basic_json& evaluate() const + { + return parent_.evaluate(key_); + } + + operator basic_json&() + { + return evaluate(); + } + + operator const basic_json&() const + { + return evaluate(); + } + + object_range_type object_range() + { + return evaluate().object_range(); + } + + const_object_range_type object_range() const + { + return evaluate().object_range(); + } + + array_range_type array_range() + { + return evaluate().array_range(); + } + + const_array_range_type array_range() const + { + return evaluate().array_range(); + } + + std::size_t size() const noexcept + { + if (!parent_.contains(key_)) + { + return 0; + } + return evaluate().size(); + } + + json_storage_kind storage_kind() const + { + return evaluate().storage_kind(); + } + + semantic_tag tag() const + { + return evaluate().tag(); + } + + json_type type() const + { + return evaluate().type(); + } + + std::size_t count(const string_view_type& name) const + { + return evaluate().count(name); + } + + allocator_type get_allocator() const + { + return evaluate().get_allocator(); + } + + uint64_t ext_tag() const + { + return evaluate().ext_tag(); + } + + bool contains(const string_view_type& key) const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + + return evaluate().contains(key); + } + + bool is_null() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_null(); + } + + bool empty() const noexcept + { + if (!parent_.contains(key_)) + { + return true; + } + return evaluate().empty(); + } + + std::size_t capacity() const + { + return evaluate().capacity(); + } + + void reserve(std::size_t n) + { + evaluate().reserve(n); + } + + void resize(std::size_t n) + { + evaluate().resize(n); + } + + template + void resize(std::size_t n, T val) + { + evaluate().resize(n,val); + } + + template + bool is(Args&&... args) const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().template is(std::forward(args)...); + } + + bool is_string() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_string(); + } + + bool is_string_view() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_string_view(); + } + + bool is_byte_string() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_byte_string(); + } + + bool is_byte_string_view() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_byte_string_view(); + } + + bool is_bignum() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_bignum(); + } + + bool is_number() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_number(); + } + bool is_bool() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_bool(); + } + + bool is_object() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_object(); + } + + bool is_array() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_array(); + } + + bool is_int64() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_int64(); + } + + bool is_uint64() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_uint64(); + } + + bool is_half() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_half(); + } + + bool is_double() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().is_double(); + } + + string_view_type as_string_view() const + { + return evaluate().as_string_view(); + } + + byte_string_view as_byte_string_view() const + { + return evaluate().as_byte_string_view(); + } + + template > + std::basic_string as_string() const + { + return evaluate().as_string(); + } + + template > + std::basic_string as_string(const SAllocator& alloc) const + { + return evaluate().as_string(alloc); + } + + template > + basic_byte_string as_byte_string() const + { + return evaluate().template as_byte_string(); + } + + template + typename std::enable_if::value,T>::type + as() const + { + return evaluate().template as(); + } + + template + typename std::enable_if::value,T>::type + as(byte_string_arg_t, semantic_tag hint) const + { + return evaluate().template as(byte_string_arg, hint); + } + + bool as_bool() const + { + return evaluate().as_bool(); + } + + double as_double() const + { + return evaluate().as_double(); + } + + template + T as_integer() const + { + return evaluate().template as_integer(); + } + + template + proxy& operator=(T&& val) + { + parent_.evaluate_with_default().insert_or_assign(key_, std::forward(val)); + return *this; + } + + basic_json& operator[](std::size_t i) + { + return evaluate_with_default().at(i); + } + + const basic_json& operator[](std::size_t i) const + { + return evaluate().at(i); + } + + proxy_type operator[](const string_view_type& key) + { + return proxy_type(*this,key); + } + + const basic_json& operator[](const string_view_type& name) const + { + return at(name); + } + + basic_json& at(const string_view_type& name) + { + return evaluate().at(name); + } + + const basic_json& at(const string_view_type& name) const + { + return evaluate().at(name); + } + + const basic_json& at_or_null(const string_view_type& name) const + { + return evaluate().at_or_null(name); + } + + const basic_json& at(std::size_t index) + { + return evaluate().at(index); + } + + const basic_json& at(std::size_t index) const + { + return evaluate().at(index); + } + + object_iterator find(const string_view_type& name) + { + return evaluate().find(name); + } + + const_object_iterator find(const string_view_type& name) const + { + return evaluate().find(name); + } + + template + T get_value_or(const string_view_type& name, U&& default_value) const + { + static_assert(std::is_copy_constructible::value, + "get_value_or: T must be copy constructible"); + static_assert(std::is_convertible::value, + "get_value_or: U must be convertible to T"); + return evaluate().template get_value_or(name,std::forward(default_value)); + } + + void shrink_to_fit() + { + evaluate_with_default().shrink_to_fit(); + } + + void clear() + { + evaluate().clear(); + } + // Remove all elements from an array or object + + object_iterator erase(const_object_iterator pos) + { + return evaluate().erase(pos); + } + // Remove a range of elements from an object + + object_iterator erase(const_object_iterator first, const_object_iterator last) + { + return evaluate().erase(first, last); + } + // Remove a range of elements from an object + + void erase(const string_view_type& name) + { + evaluate().erase(name); + } + + array_iterator erase(const_array_iterator pos) + { + return evaluate().erase(pos); + } + // Removes the element at pos + + array_iterator erase(const_array_iterator first, const_array_iterator last) + { + return evaluate().erase(first, last); + } + // Remove a range of elements from an array + + // merge + + void merge(const basic_json& source) + { + return evaluate().merge(source); + } + + void merge(basic_json&& source) + { + return evaluate().merge(std::move(source)); + } + + void merge(object_iterator hint, const basic_json& source) + { + return evaluate().merge(hint, source); + } + + void merge(object_iterator hint, basic_json&& source) + { + return evaluate().merge(hint, std::move(source)); + } + + // merge_or_update + + void merge_or_update(const basic_json& source) + { + return evaluate().merge_or_update(source); + } + + void merge_or_update(basic_json&& source) + { + return evaluate().merge_or_update(std::move(source)); + } + + void merge_or_update(object_iterator hint, const basic_json& source) + { + return evaluate().merge_or_update(hint, source); + } + + void merge_or_update(object_iterator hint, basic_json&& source) + { + return evaluate().merge_or_update(hint, std::move(source)); + } + + template + std::pair insert_or_assign(const string_view_type& name, T&& val) + { + return evaluate().insert_or_assign(name,std::forward(val)); + } + + // emplace + + template + std::pair try_emplace(const string_view_type& name, Args&&... args) + { + return evaluate().try_emplace(name,std::forward(args)...); + } + + template + object_iterator insert_or_assign(object_iterator hint, const string_view_type& name, T&& val) + { + return evaluate().insert_or_assign(hint, name, std::forward(val)); + } + + template + object_iterator try_emplace(object_iterator hint, const string_view_type& name, Args&&... args) + { + return evaluate().try_emplace(hint, name, std::forward(args)...); + } + + template + array_iterator emplace(const_array_iterator pos, Args&&... args) + { + return evaluate_with_default().emplace(pos, std::forward(args)...); + } + + template + basic_json& emplace_back(Args&&... args) + { + return evaluate_with_default().emplace_back(std::forward(args)...); + } + + template + void push_back(T&& val) + { + evaluate_with_default().push_back(std::forward(val)); + } + + template + array_iterator insert(const_array_iterator pos, T&& val) + { + return evaluate_with_default().insert(pos, std::forward(val)); + } + + template + array_iterator insert(const_array_iterator pos, InputIt first, InputIt last) + { + return evaluate_with_default().insert(pos, first, last); + } + + template + void insert(InputIt first, InputIt last) + { + evaluate_with_default().insert(first, last); + } + + template + void insert(sorted_unique_range_tag tag, InputIt first, InputIt last) + { + evaluate_with_default().insert(tag, first, last); + } + + template + void dump(Args&& ... args) const + { + evaluate().dump(std::forward(args)...); + } + + template + void dump_pretty(Args&& ... args) const + { + evaluate().dump_pretty(std::forward(args)...); + } + + void swap(basic_json& other) + { + evaluate_with_default().swap(other); + } + + friend std::basic_ostream& operator<<(std::basic_ostream& os, const proxy& o) + { + o.dump(os); + return os; + } + + std::basic_string to_string() const + { + return evaluate().to_string(); + } + + template + bool is_integer() const noexcept + { + if (!parent_.contains(key_)) + { + return false; + } + return evaluate().template is_integer(); + } + + }; + + using proxy_type = proxy; + + union + { + common_storage common_stor_; + null_storage null_stor_; + bool_storage bool_stor_; + int64_storage int64_stor_; + uint64_storage uint64_stor_; + half_storage half_stor_; + double_storage double_stor_; + short_string_storage short_string_stor_; + long_string_storage long_string_stor_; + byte_string_storage byte_string_stor_; + array_storage array_stor_; + object_storage object_stor_; + empty_object_storage empty_object_stor_; + json_const_pointer_storage json_const_pointer_stor_; + }; + + void destroy() + { + switch (storage_kind()) + { + case json_storage_kind::long_str: + destroy_var(); + break; + case json_storage_kind::byte_str: + destroy_var(); + break; + case json_storage_kind::array: + destroy_var(); + break; + case json_storage_kind::object: + destroy_var(); + break; + default: + break; + } + } + + template + void construct(Args&&... args) + { + ::new (&cast()) VariantType(std::forward(args)...); + } + + template + void destroy_var() + { + cast().~T(); + } + + template + struct identity { using type = T*; }; + public: + template + T& cast() + { + return cast(identity()); + } + + template + const T& cast() const + { + return cast(identity()); + } + private: + null_storage& cast(identity) + { + return null_stor_; + } + + const null_storage& cast(identity) const + { + return null_stor_; + } + + empty_object_storage& cast(identity) + { + return empty_object_stor_; + } + + const empty_object_storage& cast(identity) const + { + return empty_object_stor_; + } + + bool_storage& cast(identity) + { + return bool_stor_; + } + + const bool_storage& cast(identity) const + { + return bool_stor_; + } + + int64_storage& cast(identity) + { + return int64_stor_; + } + + const int64_storage& cast(identity) const + { + return int64_stor_; + } + + uint64_storage& cast(identity) + { + return uint64_stor_; + } + + const uint64_storage& cast(identity) const + { + return uint64_stor_; + } + + half_storage& cast(identity) + { + return half_stor_; + } + + const half_storage& cast(identity) const + { + return half_stor_; + } + + double_storage& cast(identity) + { + return double_stor_; + } + + const double_storage& cast(identity) const + { + return double_stor_; + } + + short_string_storage& cast(identity) + { + return short_string_stor_; + } + + const short_string_storage& cast(identity) const + { + return short_string_stor_; + } + + long_string_storage& cast(identity) + { + return long_string_stor_; + } + + const long_string_storage& cast(identity) const + { + return long_string_stor_; + } + + byte_string_storage& cast(identity) + { + return byte_string_stor_; + } + + const byte_string_storage& cast(identity) const + { + return byte_string_stor_; + } + + object_storage& cast(identity) + { + return object_stor_; + } + + const object_storage& cast(identity) const + { + return object_stor_; + } + + array_storage& cast(identity) + { + return array_stor_; + } + + const array_storage& cast(identity) const + { + return array_stor_; + } + + json_const_pointer_storage& cast(identity) + { + return json_const_pointer_stor_; + } + + const json_const_pointer_storage& cast(identity) const + { + return json_const_pointer_stor_; + } + + template + void swap_l_r(basic_json& other) + { + swap_l_r(identity(), identity(), other); + } + + template + void swap_l_r(identity,identity,basic_json& other) + { + TypeR tmpR(std::move(other.cast())); + + other.destroy(); + other.construct(std::move(cast())); + + destroy(); + construct(std::move(tmpR)); + } + + void swap_l_r(identity,identity,basic_json& other) + { + cast().swap(other.cast()); + } + + void swap_l_r(identity,identity,basic_json& other) + { + cast().swap(other.cast()); + } + + void swap_l_r(identity,identity,basic_json& other) + { + cast().swap(other.cast()); + } + + void swap_l_r(identity,identity,basic_json& other) + { + cast().swap(other.cast()); + } + + template + void swap_l(basic_json& other) + { + switch (other.storage_kind()) + { + case json_storage_kind::null : swap_l_r(other); break; + case json_storage_kind::empty_object : swap_l_r(other); break; + case json_storage_kind::boolean : swap_l_r(other); break; + case json_storage_kind::int64 : swap_l_r(other); break; + case json_storage_kind::uint64 : swap_l_r(other); break; + case json_storage_kind::half_float : swap_l_r(other); break; + case json_storage_kind::float64 : swap_l_r(other); break; + case json_storage_kind::short_str : swap_l_r(other); break; + case json_storage_kind::long_str : swap_l_r(other); break; + case json_storage_kind::byte_str : swap_l_r(other); break; + case json_storage_kind::array : swap_l_r(other); break; + case json_storage_kind::object : swap_l_r(other); break; + case json_storage_kind::const_json_pointer : swap_l_r(other); break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + + void uninitialized_copy(const basic_json& other) + { + if (is_scalar_storage(other.storage_kind())) + { + std::memcpy(static_cast(this), &other, sizeof(basic_json)); + } + else + { + switch (other.storage_kind()) + { + case json_storage_kind::long_str: + construct(other.cast()); + break; + case json_storage_kind::byte_str: + construct(other.cast()); + break; + case json_storage_kind::object: + construct(other.cast()); + break; + case json_storage_kind::array: + construct(other.cast()); + break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + void uninitialized_copy_a(const basic_json& other, const Allocator& alloc) + { + if (is_scalar_storage(other.storage_kind())) + { + std::memcpy(static_cast(this), &other, sizeof(basic_json)); + } + else + { + switch (other.storage_kind()) + { + case json_storage_kind::long_str: + construct(other.cast(),alloc); + break; + case json_storage_kind::byte_str: + construct(other.cast(),alloc); + break; + case json_storage_kind::array: + construct(other.cast(),alloc); + break; + case json_storage_kind::object: + construct(other.cast(),alloc); + break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + void uninitialized_move(basic_json&& other) noexcept + { + if (is_scalar_storage(other.storage_kind())) + { + std::memcpy(static_cast(this), &other, sizeof(basic_json)); + } + else + { + switch (other.storage_kind()) + { + case json_storage_kind::long_str: + construct(std::move(other.cast())); + break; + case json_storage_kind::byte_str: + construct(std::move(other.cast())); + break; + case json_storage_kind::array: + construct(std::move(other.cast())); + break; + case json_storage_kind::object: + construct(std::move(other.cast())); + break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + void uninitialized_move_a(std::true_type /* stateless allocator */, + basic_json&& other, const Allocator&) noexcept + { + uninitialized_move(std::move(other)); + } + + void uninitialized_move_a(std::false_type /* stateful allocator */, + basic_json&& other, const Allocator& alloc) noexcept + { + if (is_scalar_storage(other.storage_kind())) + { + std::memcpy(static_cast(this), &other, sizeof(basic_json)); + } + else + { + switch (other.storage_kind()) + { + case json_storage_kind::long_str: + construct(std::move(other.cast()), alloc); + break; + case json_storage_kind::byte_str: + construct(std::move(other.cast()), alloc); + break; + case json_storage_kind::array: + construct(std::move(other.cast()), alloc); + break; + case json_storage_kind::object: + construct(std::move(other.cast()), alloc); + break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + void copy_assignment(const basic_json& other) + { + if (is_scalar_storage(other.storage_kind())) + { + destroy(); + std::memcpy(static_cast(this), &other, sizeof(basic_json)); + } + else + { + switch (other.storage_kind()) + { + case json_storage_kind::long_str: + if (storage_kind() == json_storage_kind::long_str) + { + cast().assign(other.cast()); + } + else + { + destroy(); + uninitialized_copy(other); + } + break; + case json_storage_kind::byte_str: + if (storage_kind() == json_storage_kind::byte_str) + { + cast().assign(other.cast()); + } + else + { + destroy(); + uninitialized_copy(other); + } + break; + case json_storage_kind::array: + if (storage_kind() == json_storage_kind::array) + { + cast().assign(other.cast()); + } + else + { + destroy(); + uninitialized_copy(other); + } + break; + case json_storage_kind::object: + if (storage_kind() == json_storage_kind::object) + { + cast().assign(other.cast()); + } + else + { + destroy(); + uninitialized_copy(other); + } + break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + void move_assignment(basic_json&& other) + { + if (is_scalar_storage(other.storage_kind())) + { + destroy(); + std::memcpy(static_cast(this), &other, sizeof(basic_json)); + } + else + { + switch (other.storage_kind()) + { + case json_storage_kind::long_str: + if (storage_kind() == json_storage_kind::long_str) + { + cast().assign(std::move(other.cast())); + } + else + { + destroy(); + uninitialized_move(std::move(other)); + } + break; + case json_storage_kind::byte_str: + if (storage_kind() == json_storage_kind::byte_str) + { + cast().assign(std::move(other.cast())); + } + else + { + destroy(); + uninitialized_move(std::move(other)); + } + break; + case json_storage_kind::array: + if (storage_kind() == json_storage_kind::array) + { + cast().assign(std::move(other.cast())); + } + else + { + destroy(); + uninitialized_move(std::move(other)); + } + break; + case json_storage_kind::object: + if (storage_kind() == json_storage_kind::object) + { + cast().assign(std::move(other.cast())); + } + else + { + destroy(); + uninitialized_move(std::move(other)); + } + break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + basic_json& evaluate_with_default() + { + return *this; + } + + basic_json& evaluate(const string_view_type& name) + { + return at(name); + } + + const basic_json& evaluate(const string_view_type& name) const + { + return at(name); + } + + public: + + basic_json& evaluate() + { + return *this; + } + + const basic_json& evaluate() const + { + return *this; + } + + basic_json& operator=(const basic_json& other) + { + if (this != &other) + { + copy_assignment(other); + } + return *this; + } + + basic_json& operator=(basic_json&& other) noexcept + { + if (this != &other) + { + move_assignment(std::move(other)); + } + return *this; + } + + json_storage_kind storage_kind() const + { + // It is legal to access 'common_stor_.storage_kind_' even though + // common_stor_ is not the active member of the union because 'storage_kind_' + // is a part of the common initial sequence of all union members + // as defined in 11.4-25 of the Standard. + return static_cast(common_stor_.storage_kind_); + } + + json_type type() const + { + switch(storage_kind()) + { + case json_storage_kind::null: + return json_type::null_value; + case json_storage_kind::boolean: + return json_type::bool_value; + case json_storage_kind::int64: + return json_type::int64_value; + case json_storage_kind::uint64: + return json_type::uint64_value; + case json_storage_kind::half_float: + return json_type::half_value; + case json_storage_kind::float64: + return json_type::double_value; + case json_storage_kind::short_str: + case json_storage_kind::long_str: + return json_type::string_value; + case json_storage_kind::byte_str: + return json_type::byte_string_value; + case json_storage_kind::array: + return json_type::array_value; + case json_storage_kind::empty_object: + case json_storage_kind::object: + return json_type::object_value; + case json_storage_kind::const_json_pointer: + return cast().value()->type(); + default: + JSONCONS_UNREACHABLE(); + break; + } + } + + semantic_tag tag() const + { + // It is legal to access 'common_stor_.tag_' even though + // common_stor_ is not the active member of the union because 'tag_' + // is a part of the common initial sequence of all union members + // as defined in 11.4-25 of the Standard. + switch(storage_kind()) + { + case json_storage_kind::const_json_pointer: + return cast().value()->tag(); + default: + return common_stor_.tag_; + } + } + + std::size_t size() const + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().size(); + case json_storage_kind::empty_object: + return 0; + case json_storage_kind::object: + return cast().value().size(); + case json_storage_kind::const_json_pointer: + return cast().value()->size(); + default: + return 0; + } + } + + string_view_type as_string_view() const + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + return string_view_type(cast().data(),cast().length()); + case json_storage_kind::long_str: + return string_view_type(cast().data(),cast().length()); + case json_storage_kind::const_json_pointer: + return cast().value()->as_string_view(); + default: + JSONCONS_THROW(json_runtime_error("Not a string")); + } + } + + template > + basic_byte_string as_byte_string() const + { + using byte_string_type = basic_byte_string; + std::error_code ec; + + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + { + value_converter converter; + byte_string_type v = converter.convert(as_string_view(),tag(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + return v; + } + case json_storage_kind::byte_str: + return basic_byte_string(cast().data(),cast().length()); + case json_storage_kind::const_json_pointer: + return cast().value()->as_byte_string(); + default: + JSONCONS_THROW(json_runtime_error("Not a byte string")); + } + } + + byte_string_view as_byte_string_view() const + { + switch (storage_kind()) + { + case json_storage_kind::byte_str: + return byte_string_view(cast().data(),cast().length()); + case json_storage_kind::const_json_pointer: + return cast().value()->as_byte_string_view(); + default: + JSONCONS_THROW(json_runtime_error("Not a byte string")); + } + } + + int compare(const basic_json& rhs) const noexcept + { + if (this == &rhs) + { + return 0; + } + switch (storage_kind()) + { + case json_storage_kind::const_json_pointer: + switch (rhs.storage_kind()) + { + case json_storage_kind::const_json_pointer: + return (cast().value())->compare(*(rhs.cast().value())); + default: + return (cast().value())->compare(rhs); + } + break; + case json_storage_kind::null: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + case json_storage_kind::empty_object: + switch (rhs.storage_kind()) + { + case json_storage_kind::empty_object: + return 0; + case json_storage_kind::object: + return rhs.empty() ? 0 : -1; + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + break; + case json_storage_kind::boolean: + switch (rhs.storage_kind()) + { + case json_storage_kind::boolean: + return static_cast(cast().value()) - static_cast(rhs.cast().value()); + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + break; + case json_storage_kind::int64: + switch (rhs.storage_kind()) + { + case json_storage_kind::int64: + { + if (cast().value() == rhs.cast().value()) + return 0; + return cast().value() < rhs.cast().value() ? -1 : 1; + } + case json_storage_kind::uint64: + if (cast().value() < 0) + return -1; + else if (static_cast(cast().value()) == rhs.cast().value()) + return 0; + else + return static_cast(cast().value()) < rhs.cast().value() ? -1 : 1; + case json_storage_kind::float64: + { + double r = static_cast(cast().value()) - rhs.cast().value(); + return r == 0.0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + break; + case json_storage_kind::uint64: + switch (rhs.storage_kind()) + { + case json_storage_kind::int64: + if (rhs.cast().value() < 0) + return 1; + else if (cast().value() == static_cast(rhs.cast().value())) + return 0; + else + return cast().value() < static_cast(rhs.cast().value()) ? -1 : 1; + case json_storage_kind::uint64: + if (cast().value() == static_cast(rhs.cast().value())) + return 0; + else + return cast().value() < static_cast(rhs.cast().value()) ? -1 : 1; + case json_storage_kind::float64: + { + auto r = static_cast(cast().value()) - rhs.cast().value(); + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + break; + case json_storage_kind::float64: + switch (rhs.storage_kind()) + { + case json_storage_kind::int64: + { + auto r = cast().value() - static_cast(rhs.cast().value()); + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::uint64: + { + auto r = cast().value() - static_cast(rhs.cast().value()); + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::float64: + { + auto r = cast().value() - rhs.cast().value(); + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + if (is_string_storage(rhs.storage_kind())) + { + double val1 = as_double(); + double val2 = rhs.as_double(); + auto r = val1 - val2; + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + else + { + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + } + break; + case json_storage_kind::short_str: + case json_storage_kind::long_str: + if (is_number_tag(tag())) + { + double val1 = as_double(); + switch (rhs.storage_kind()) + { + case json_storage_kind::int64: + { + auto r = val1 - static_cast(rhs.cast().value()); + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::uint64: + { + auto r = val1 - static_cast(rhs.cast().value()); + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::float64: + { + auto r = val1 - rhs.cast().value(); + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + if (is_string_storage(rhs.storage_kind())) + { + double val2 = rhs.as_double(); + auto r = val1 - val2; + return r == 0 ? 0 : (r < 0.0 ? -1 : 1); + } + else + { + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + } + } + else + { + // compare regular text + switch (rhs.storage_kind()) + { + case json_storage_kind::short_str: + return as_string_view().compare(rhs.as_string_view()); + case json_storage_kind::long_str: + return as_string_view().compare(rhs.as_string_view()); + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + } + break; + case json_storage_kind::byte_str: + switch (rhs.storage_kind()) + { + case json_storage_kind::byte_str: + { + return as_byte_string_view().compare(rhs.as_byte_string_view()); + } + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + break; + case json_storage_kind::array: + switch (rhs.storage_kind()) + { + case json_storage_kind::array: + { + if (cast().value() == rhs.cast().value()) + return 0; + else + return cast().value() < rhs.cast().value() ? -1 : 1; + } + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + break; + case json_storage_kind::object: + switch (rhs.storage_kind()) + { + case json_storage_kind::empty_object: + return empty() ? 0 : 1; + case json_storage_kind::object: + { + if (cast().value() == rhs.cast().value()) + return 0; + else + return cast().value() < rhs.cast().value() ? -1 : 1; + } + case json_storage_kind::const_json_pointer: + return compare(*(rhs.cast().value())); + default: + return static_cast(storage_kind()) - static_cast((int)rhs.storage_kind()); + } + break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + + void swap(basic_json& other) noexcept + { + if (this == &other) + { + return; + } + + switch (storage_kind()) + { + case json_storage_kind::null: swap_l(other); break; + case json_storage_kind::empty_object : swap_l(other); break; + case json_storage_kind::boolean: swap_l(other); break; + case json_storage_kind::int64: swap_l(other); break; + case json_storage_kind::uint64: swap_l(other); break; + case json_storage_kind::half_float: swap_l(other); break; + case json_storage_kind::float64: swap_l(other); break; + case json_storage_kind::short_str: swap_l(other); break; + case json_storage_kind::long_str: swap_l(other); break; + case json_storage_kind::byte_str: swap_l(other); break; + case json_storage_kind::array: swap_l(other); break; + case json_storage_kind::object: swap_l(other); break; + case json_storage_kind::const_json_pointer: swap_l(other); break; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + // from string + + template + static + typename std::enable_if::value,basic_json>::type + parse(const Source& source, + const basic_json_decode_options& options = basic_json_options()) + { + json_decoder decoder; + basic_json_parser parser(options); + + auto r = unicode_traits::detect_encoding_from_bom(source.data(), source.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + JSONCONS_THROW(ser_error(json_errc::illegal_unicode_character,parser.line(),parser.column())); + } + std::size_t offset = (r.ptr - source.data()); + parser.update(source.data()+offset,source.size()-offset); + parser.parse_some(decoder); + parser.finish_parse(decoder); + parser.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json string")); + } + return decoder.get_result(); + } + + template + static + typename std::enable_if::value,basic_json>::type + parse(const allocator_set& alloc_set, const Source& source, + const basic_json_decode_options& options = basic_json_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + basic_json_parser parser(options, alloc_set.get_temp_allocator()); + + auto r = unicode_traits::detect_encoding_from_bom(source.data(), source.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + JSONCONS_THROW(ser_error(json_errc::illegal_unicode_character,parser.line(),parser.column())); + } + std::size_t offset = (r.ptr - source.data()); + parser.update(source.data()+offset,source.size()-offset); + parser.parse_some(decoder); + parser.finish_parse(decoder); + parser.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json string")); + } + return decoder.get_result(); + } + + static basic_json parse(const char_type* str, std::size_t length, + const basic_json_decode_options& options = basic_json_options()) + { + return parse(jsoncons::string_view(str,length), options); + } + + static basic_json parse(const char_type* source, + const basic_json_decode_options& options = basic_json_options()) + { + return parse(jsoncons::basic_string_view(source), options); + } + + template + static basic_json parse(const allocator_set& alloc_set, const char_type* source, + const basic_json_decode_options& options = basic_json_options()) + { + return parse(alloc_set, jsoncons::basic_string_view(source), options); + } + + template + static basic_json parse(const allocator_set& alloc_set, + const char_type* str, std::size_t length, + const basic_json_decode_options& options = basic_json_options()) + { + return parse(alloc_set, jsoncons::basic_string_view(str, length), options); + } + + // from stream + + static basic_json parse(std::basic_istream& is, + const basic_json_decode_options& options = basic_json_options()) + { + json_decoder decoder; + basic_json_reader,Allocator> reader(is, decoder, options); + reader.read_next(); + reader.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json stream")); + } + return decoder.get_result(); + } + + template + static basic_json parse(const allocator_set& alloc_set, std::basic_istream& is, + const basic_json_decode_options& options = basic_json_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + basic_json_reader,Allocator> reader(is, decoder, options, alloc_set.get_temp_allocator()); + reader.read_next(); + reader.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json stream")); + } + return decoder.get_result(); + } + + // from iterator + + template + static basic_json parse(InputIt first, InputIt last, + const basic_json_decode_options& options = basic_json_options()) + { + json_decoder decoder; + basic_json_reader,Allocator> reader(iterator_source(std::forward(first), + std::forward(last)), decoder, options); + reader.read_next(); + reader.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json from iterator pair")); + } + return decoder.get_result(); + } + + template + static basic_json parse(const allocator_set& alloc_set, InputIt first, InputIt last, + const basic_json_decode_options& options = basic_json_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + basic_json_reader,Allocator> reader(iterator_source(std::forward(first), + std::forward(last)), + decoder, options, alloc_set.get_temp_allocator()); + reader.read_next(); + reader.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json from iterator pair")); + } + return decoder.get_result(); + } + +#if !defined(JSONCONS_NO_DEPRECATED) + + static basic_json parse(const char_type* s, + const basic_json_decode_options& options, + std::function err_handler) + { + return parse(jsoncons::basic_string_view(s), options, err_handler); + } + + static basic_json parse(const char_type* s, + std::function err_handler) + { + return parse(jsoncons::basic_string_view(s), basic_json_decode_options(), err_handler); + } + + static basic_json parse(std::basic_istream& is, + const basic_json_decode_options& options, + std::function err_handler) + { + json_decoder decoder; + basic_json_reader> reader(is, decoder, options, err_handler); + reader.read_next(); + reader.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json stream")); + } + return decoder.get_result(); + } + + static basic_json parse(std::basic_istream& is, std::function err_handler) + { + return parse(is, basic_json_decode_options(), err_handler); + } + + template + static basic_json parse(InputIt first, InputIt last, + const basic_json_decode_options& options, + std::function err_handler) + { + json_decoder decoder; + basic_json_reader> reader(iterator_source(std::forward(first),std::forward(last)), decoder, options, err_handler); + reader.read_next(); + reader.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json from iterator pair")); + } + return decoder.get_result(); + } + + template + static + typename std::enable_if::value,basic_json>::type + parse(const Source& source, + const basic_json_decode_options& options, + std::function err_handler) + { + json_decoder decoder; + basic_json_parser parser(options,err_handler); + + auto r = unicode_traits::detect_encoding_from_bom(source.data(), source.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + JSONCONS_THROW(ser_error(json_errc::illegal_unicode_character,parser.line(),parser.column())); + } + std::size_t offset = (r.ptr - source.data()); + parser.update(source.data()+offset,source.size()-offset); + parser.parse_some(decoder); + parser.finish_parse(decoder); + parser.check_done(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(json_errc::source_error, "Failed to parse json string")); + } + return decoder.get_result(); + } + + template + static + typename std::enable_if::value,basic_json>::type + parse(const Source& source, + std::function err_handler) + { + return parse(source, basic_json_decode_options(), err_handler); + } + + template + static basic_json parse(InputIt first, InputIt last, + std::function err_handler) + { + return parse(first, last, basic_json_decode_options(), err_handler); + } +#endif + static basic_json make_array() + { + return basic_json(array()); + } + + static basic_json make_array(const array& alloc) + { + return basic_json(alloc); + } + + static basic_json make_array(const array& a, allocator_type alloc) + { + return basic_json(a, semantic_tag::none, alloc); + } + + static basic_json make_array(std::initializer_list init, const Allocator& alloc = Allocator()) + { + return array(std::move(init),alloc); + } + + static basic_json make_array(std::size_t n, const Allocator& alloc = Allocator()) + { + return array(n,alloc); + } + + template + static basic_json make_array(std::size_t n, const T& val, const Allocator& alloc = Allocator()) + { + return basic_json::array(n, val,alloc); + } + + template + static typename std::enable_if::type make_array(std::size_t n) + { + return array(n); + } + + template + static typename std::enable_if::type make_array(std::size_t n, const T& val, const Allocator& alloc = Allocator()) + { + return array(n,val,alloc); + } + + template + static typename std::enable_if<(dim>1),basic_json>::type make_array(std::size_t n, Args... args) + { + const size_t dim1 = dim - 1; + + basic_json val = make_array(std::forward(args)...); + val.resize(n); + for (std::size_t i = 0; i < n; ++i) + { + val[i] = make_array(std::forward(args)...); + } + return val; + } + + static const basic_json& null() + { + static const basic_json a_null = basic_json(null_type(), semantic_tag::none); + return a_null; + } + + basic_json() + { + construct(semantic_tag::none); + } + + explicit basic_json(const Allocator&) + { + construct(semantic_tag::none); + } + + basic_json(semantic_tag tag) + { + construct(tag); + } + + basic_json(const basic_json& other) + { + uninitialized_copy(other); + } + + basic_json(const basic_json& other, const Allocator& alloc) + { + uninitialized_copy_a(other,alloc); + } + + basic_json(basic_json&& other) noexcept + { + uninitialized_move(std::move(other)); + } + + template + basic_json(basic_json&& other, const Allocator& alloc) noexcept + { + uninitialized_move_a(extension_traits::is_stateless{}, std::move(other), alloc); + } + + explicit basic_json(json_object_arg_t, + semantic_tag tag, + const Allocator& alloc = Allocator()) + { + construct(object(alloc), tag); + } + + explicit basic_json(json_object_arg_t, const Allocator& alloc = Allocator()) + { + construct(object(alloc), semantic_tag::none); + } + + template + basic_json(json_object_arg_t, + InputIt first, InputIt last, + semantic_tag tag = semantic_tag::none, + const Allocator& alloc = Allocator()) + { + construct(object(first,last,alloc), tag); + } + + basic_json(json_object_arg_t, + std::initializer_list,basic_json>> init, + semantic_tag tag = semantic_tag::none, + const Allocator& alloc = Allocator()) + { + construct(object(init,alloc), tag); + } + + explicit basic_json(json_array_arg_t, const Allocator& alloc = Allocator()) + { + construct(array(alloc), semantic_tag::none); + } + + explicit basic_json(json_array_arg_t, + semantic_tag tag, + const Allocator& alloc = Allocator()) + { + construct(array(alloc), tag); + } + + template + basic_json(json_array_arg_t, + InputIt first, InputIt last, + semantic_tag tag = semantic_tag::none, + const Allocator& alloc = Allocator()) + { + construct(array(first,last,alloc), tag); + } + + basic_json(json_array_arg_t, + std::initializer_list init, + semantic_tag tag = semantic_tag::none, + const Allocator& alloc = Allocator()) + { + construct(array(init,alloc), tag); + } + + basic_json(json_const_pointer_arg_t, const basic_json* p) noexcept + { + if (p == nullptr) + { + construct(semantic_tag::none); + } + else + { + construct(p); + } + } + + basic_json(const array& val, semantic_tag tag = semantic_tag::none) + { + construct(val, tag); + } + + basic_json(array&& val, semantic_tag tag = semantic_tag::none) + { + construct(std::move(val), tag); + } + + basic_json(const object& val, semantic_tag tag = semantic_tag::none) + { + construct(val, tag); + } + + basic_json(object&& val, semantic_tag tag = semantic_tag::none) + { + construct(std::move(val), tag); + } + + template ::value && !extension_traits::is_basic_json::value>::type> + basic_json(const T& val) + : basic_json(json_type_traits::to_json(val)) + { + } + + template ::value && !extension_traits::is_basic_json::value>::type> + basic_json(const T& val, const Allocator& alloc) + : basic_json(json_type_traits::to_json(val,alloc)) + { + } + + basic_json(const string_type& s) + : basic_json(s.data(), s.size(), semantic_tag::none, s.get_allocator()) + { + } + + basic_json(const string_view_type& s, const allocator_type& alloc = allocator_type()) + : basic_json(s.data(), s.size(), semantic_tag::none, alloc) + { + } + + basic_json(const string_type& s, semantic_tag tag) + : basic_json(s.data(), s.size(), tag, s.get_allocator()) + { + } + + basic_json(const string_type& s, semantic_tag tag, const allocator_type& alloc) + : basic_json(s.data(), s.size(), tag, alloc) + { + } + + basic_json(const char_type* s, semantic_tag tag = semantic_tag::none) + : basic_json(s, char_traits_type::length(s), tag) + { + } + + basic_json(const char_type* s, semantic_tag tag, const allocator_type& alloc) + : basic_json(s, char_traits_type::length(s), tag, alloc) + { + } + + basic_json(const char_type* s, const Allocator& alloc) + : basic_json(s, char_traits_type::length(s), semantic_tag::none, alloc) + { + } + + basic_json(const char_type* s, std::size_t length, semantic_tag tag = semantic_tag::none) + { + if (length <= short_string_storage::max_length) + { + construct(tag, s, static_cast(length)); + } + else + { + construct(tag, s, length, char_allocator_type()); + } + } + + basic_json(const char_type* s, std::size_t length, semantic_tag tag, const Allocator& alloc) + { + if (length <= short_string_storage::max_length) + { + construct(tag, s, static_cast(length)); + } + else + { + construct(tag, s, length, alloc); + } + } + + basic_json(half_arg_t, uint16_t val, semantic_tag tag = semantic_tag::none) + { + construct(val, tag); + } + + basic_json(half_arg_t, uint16_t val, semantic_tag tag, const allocator_type&) + { + construct(val, tag); + } + + basic_json(double val, semantic_tag tag) + { + construct(val, tag); + } + + basic_json(double val, semantic_tag tag, const allocator_type&) + { + construct(val, tag); + } + + template + basic_json(IntegerType val, semantic_tag tag, + typename std::enable_if::value && sizeof(IntegerType) <= sizeof(uint64_t), int>::type = 0) + { + construct(val, tag); + } + + template + basic_json(IntegerType val, semantic_tag tag, Allocator, + typename std::enable_if::value && sizeof(IntegerType) <= sizeof(uint64_t), int>::type = 0) + { + construct(val, tag); + } + + template + basic_json(IntegerType val, semantic_tag, Allocator alloc = Allocator(), + typename std::enable_if::value && sizeof(uint64_t) < sizeof(IntegerType), int>::type = 0) + { + std::basic_string s; + jsoncons::detail::from_integer(val, s); + if (s.length() <= short_string_storage::max_length) + { + construct(semantic_tag::bigint, s.data(), static_cast(s.length())); + } + else + { + construct(semantic_tag::bigint, s.data(), s.length(), alloc); + } + } + + template + basic_json(IntegerType val, semantic_tag tag, + typename std::enable_if::value && sizeof(IntegerType) <= sizeof(int64_t),int>::type = 0) + { + construct(val, tag); + } + + template + basic_json(IntegerType val, semantic_tag tag, Allocator, + typename std::enable_if::value && sizeof(IntegerType) <= sizeof(int64_t),int>::type = 0) + { + construct(val, tag); + } + + template + basic_json(IntegerType val, semantic_tag, Allocator alloc = Allocator(), + typename std::enable_if::value && sizeof(int64_t) < sizeof(IntegerType),int>::type = 0) + { + std::basic_string s; + jsoncons::detail::from_integer(val, s); + if (s.length() <= short_string_storage::max_length) + { + construct(semantic_tag::bigint, s.data(), static_cast(s.length())); + } + else + { + construct(semantic_tag::bigint, s.data(), s.length(), alloc); + } + } + + basic_json(null_type, semantic_tag tag) + { + construct(tag); + } + + basic_json(null_type, semantic_tag tag, const Allocator&) + { + construct(tag); + } + + basic_json(bool val, semantic_tag tag) + { + construct(val,tag); + } + + basic_json(bool val, semantic_tag tag, const Allocator&) + { + construct(val,tag); + } + + basic_json(const string_view_type& sv, semantic_tag tag, const allocator_type& alloc = allocator_type()) + : basic_json(sv.data(), sv.length(), tag, alloc) + { + } + + template + basic_json(byte_string_arg_t, const Source& source, + semantic_tag tag = semantic_tag::none, + const Allocator& alloc = Allocator(), + typename std::enable_if::value,int>::type = 0) + { + auto bytes = jsoncons::span(reinterpret_cast(source.data()), source.size()); + construct(tag, bytes.data(), bytes.size(), 0, alloc); + } + + template + basic_json(byte_string_arg_t, const Source& source, + uint64_t ext_tag, + const Allocator& alloc = Allocator(), + typename std::enable_if::value,int>::type = 0) + { + auto bytes = jsoncons::span(reinterpret_cast(source.data()), source.size()); + construct(semantic_tag::ext, bytes.data(), bytes.size(), ext_tag, alloc); + } + + ~basic_json() noexcept + { + destroy(); + } + + template + basic_json& operator=(const T& val) + { + *this = json_type_traits::to_json(val); + return *this; + } + + basic_json& operator=(const char_type* s) + { + *this = basic_json(s, char_traits_type::length(s), semantic_tag::none); + return *this; + } + + basic_json& operator[](std::size_t i) + { + return at(i); + } + + const basic_json& operator[](std::size_t i) const + { + return at(i); + } + + proxy_type operator[](const string_view_type& name) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + JSONCONS_FALLTHROUGH; + case json_storage_kind::object: + return proxy_type(*this, name); + break; + default: + JSONCONS_THROW(not_an_object(name.data(),name.length())); + break; + } + } + + const basic_json& operator[](const string_view_type& name) const + { + return at(name); + } + + + template + typename std::enable_if::value>::type + dump(CharContainer& cont, + const basic_json_encode_options& options = basic_json_options(), + indenting indent = indenting::no_indent) const + { + std::error_code ec; + dump(cont, options, indent, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value>::type + dump_pretty(CharContainer& cont, + const basic_json_encode_options& options = basic_json_options()) const + { + std::error_code ec; + dump_pretty(cont, options, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + void dump(std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_options(), + indenting indent = indenting::no_indent) const + { + std::error_code ec; + dump(os, options, indent, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value>::type + dump_pretty(CharContainer& cont, + const basic_json_encode_options& options, + std::error_code& ec) const + { + basic_json_encoder> encoder(cont, options); + dump(encoder, ec); + } + + template + typename std::enable_if::value>::type + dump_pretty(CharContainer& cont, + std::error_code& ec) const + { + dump_pretty(cont, basic_json_encode_options(), ec); + } + + void dump_pretty(std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_options()) const + { + std::error_code ec; + dump_pretty(os, options, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + void dump_pretty(std::basic_ostream& os, + const basic_json_encode_options& options, + std::error_code& ec) const + { + basic_json_encoder encoder(os, options); + dump(encoder, ec); + } + + void dump_pretty(std::basic_ostream& os, + std::error_code& ec) const + { + dump_pretty(os, basic_json_encode_options(), ec); + } + + template + typename std::enable_if::value>::type + dump(CharContainer& cont, indenting indent) const + { + std::error_code ec; + + dump(cont, indent, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + void dump(std::basic_ostream& os, + indenting indent) const + { + std::error_code ec; + + dump(os, indent, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + void dump(basic_json_visitor& visitor) const + { + std::error_code ec; + dump(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // dump + template + typename std::enable_if::value>::type + dump(CharContainer& cont, + const basic_json_encode_options& options, + std::error_code& ec) const + { + basic_compact_json_encoder> encoder(cont, options); + dump(encoder, ec); + } + + template + typename std::enable_if::value>::type + dump(CharContainer& cont, + std::error_code& ec) const + { + basic_compact_json_encoder> encoder(cont); + dump(encoder, ec); + } + + void dump(std::basic_ostream& os, + const basic_json_encode_options& options, + std::error_code& ec) const + { + basic_compact_json_encoder encoder(os, options); + dump(encoder, ec); + } + + void dump(std::basic_ostream& os, + std::error_code& ec) const + { + basic_compact_json_encoder encoder(os); + dump(encoder, ec); + } + + // legacy + template + typename std::enable_if::value>::type + dump(CharContainer& cont, + const basic_json_encode_options& options, + indenting indent, + std::error_code& ec) const + { + if (indent == indenting::indent) + { + dump_pretty(cont, options, ec); + } + else + { + dump(cont, options, ec); + } + } + + template + typename std::enable_if::value>::type + dump(CharContainer& cont, + indenting indent, + std::error_code& ec) const + { + if (indent == indenting::indent) + { + dump_pretty(cont, ec); + } + else + { + dump(cont, ec); + } + } + + void dump(std::basic_ostream& os, + const basic_json_encode_options& options, + indenting indent, + std::error_code& ec) const + { + if (indent == indenting::indent) + { + dump_pretty(os, options, ec); + } + else + { + dump(os, options, ec); + } + } + + void dump(std::basic_ostream& os, + indenting indent, + std::error_code& ec) const + { + if (indent == indenting::indent) + { + dump_pretty(os, ec); + } + else + { + dump(os, ec); + } + } + // end legacy + + void dump(basic_json_visitor& visitor, + std::error_code& ec) const + { + dump_noflush(visitor, ec); + if (ec) + { + return; + } + visitor.flush(); + } + + bool is_null() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::null: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_null(); + default: + return false; + } + } + + allocator_type get_allocator() const + { + switch (storage_kind()) + { + case json_storage_kind::long_str: + { + return cast().get_allocator(); + } + case json_storage_kind::byte_str: + { + return cast().get_allocator(); + } + case json_storage_kind::array: + { + return cast().get_allocator(); + } + case json_storage_kind::object: + { + return cast().get_allocator(); + } + default: + return allocator_type(); + } + } + + uint64_t ext_tag() const + { + switch (storage_kind()) + { + case json_storage_kind::byte_str: + { + return cast().ext_tag(); + } + case json_storage_kind::const_json_pointer: + return cast().value()->ext_tag(); + default: + return 0; + } + } + + bool contains(const string_view_type& key) const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::object: + { + auto it = cast().value().find(key); + return it != cast().value().end(); + } + case json_storage_kind::const_json_pointer: + return cast().value()->contains(key); + default: + return false; + } + } + + std::size_t count(const string_view_type& key) const + { + switch (storage_kind()) + { + case json_storage_kind::object: + { + auto it = cast().value().find(key); + if (it == cast().value().end()) + { + return 0; + } + std::size_t count = 0; + while (it != cast().value().end()&& it->key() == key) + { + ++count; + ++it; + } + return count; + } + case json_storage_kind::const_json_pointer: + return cast().value()->count(key); + default: + return 0; + } + } + + template + bool is(Args&&... args) const noexcept + { + return json_type_traits::is(*this,std::forward(args)...); + } + + bool is_string() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_string(); + default: + return false; + } + } + + bool is_string_view() const noexcept + { + return is_string(); + } + + bool is_byte_string() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::byte_str: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_byte_string(); + default: + return false; + } + } + + bool is_byte_string_view() const noexcept + { + return is_byte_string(); + } + + bool is_bignum() const + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + return jsoncons::detail::is_base10(as_string_view().data(), as_string_view().length()); + case json_storage_kind::int64: + case json_storage_kind::uint64: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_bignum(); + default: + return false; + } + } + + bool is_bool() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::boolean: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_bool(); + default: + return false; + } + } + + bool is_object() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + case json_storage_kind::object: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_object(); + default: + return false; + } + } + + bool is_array() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::array: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_array(); + default: + return false; + } + } + + bool is_int64() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::int64: + return true; + case json_storage_kind::uint64: + return as_integer() <= static_cast((std::numeric_limits::max)()); + case json_storage_kind::const_json_pointer: + return cast().value()->is_int64(); + default: + return false; + } + } + + bool is_uint64() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::uint64: + return true; + case json_storage_kind::int64: + return as_integer() >= 0; + case json_storage_kind::const_json_pointer: + return cast().value()->is_uint64(); + default: + return false; + } + } + + bool is_half() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::half_float: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_half(); + default: + return false; + } + } + + bool is_double() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::float64: + return true; + case json_storage_kind::const_json_pointer: + return cast().value()->is_double(); + default: + return false; + } + } + + bool is_number() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::int64: + case json_storage_kind::uint64: + case json_storage_kind::half_float: + case json_storage_kind::float64: + return true; + case json_storage_kind::short_str: + case json_storage_kind::long_str: + return tag() == semantic_tag::bigint || + tag() == semantic_tag::bigdec || + tag() == semantic_tag::bigfloat; + case json_storage_kind::const_json_pointer: + return cast().value()->is_number(); + default: + return false; + } + } + + bool empty() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::byte_str: + return cast().length() == 0; + break; + case json_storage_kind::short_str: + return cast().length() == 0; + case json_storage_kind::long_str: + return cast().length() == 0; + case json_storage_kind::array: + return cast().value().empty(); + case json_storage_kind::empty_object: + return true; + case json_storage_kind::object: + return cast().value().empty(); + case json_storage_kind::const_json_pointer: + return cast().value()->empty(); + default: + return false; + } + } + + std::size_t capacity() const + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().capacity(); + case json_storage_kind::object: + return cast().value().capacity(); + case json_storage_kind::const_json_pointer: + return cast().value()->capacity(); + default: + return 0; + } + } + + template + void create_object_implicitly() + { + create_object_implicitly(extension_traits::is_stateless()); + } + + void create_object_implicitly(std::false_type) + { + JSONCONS_THROW(json_runtime_error("Cannot create object implicitly - allocator is stateful.")); + } + + void create_object_implicitly(std::true_type) + { + *this = basic_json(json_object_arg, tag()); + } + + void reserve(std::size_t n) + { + if (n > 0) + { + switch (storage_kind()) + { + case json_storage_kind::array: + cast().value().reserve(n); + break; + case json_storage_kind::empty_object: + { + create_object_implicitly(); + cast().value().reserve(n); + } + break; + case json_storage_kind::object: + { + cast().value().reserve(n); + } + break; + default: + break; + } + } + } + + void resize(std::size_t n) + { + switch (storage_kind()) + { + case json_storage_kind::array: + cast().value().resize(n); + break; + default: + break; + } + } + + template + void resize(std::size_t n, T val) + { + switch (storage_kind()) + { + case json_storage_kind::array: + cast().value().resize(n, val); + break; + default: + break; + } + } + + template + typename std::enable_if::value,T>::type + as() const + { + T val = json_type_traits::as(*this); + return val; + } + + template + typename std::enable_if<(!extension_traits::is_string::value && + extension_traits::is_back_insertable_byte_container::value) || + extension_traits::is_basic_byte_string::value,T>::type + as(byte_string_arg_t, semantic_tag hint) const + { + std::error_code ec; + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + { + switch (tag()) + { + case semantic_tag::base16: + case semantic_tag::base64: + case semantic_tag::base64url: + { + value_converter,T> converter; + T v = converter.convert(as_string_view(),tag(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + return v; + } + default: + { + value_converter, T> converter; + T v = converter.convert(as_string_view(), hint, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + return T(v.begin(),v.end()); + } + break; + } + break; + } + case json_storage_kind::byte_str: + return T(as_byte_string_view().begin(), as_byte_string_view().end()); + case json_storage_kind::const_json_pointer: + return cast().value()->template as(byte_string_arg, hint); + default: + JSONCONS_THROW(json_runtime_error("Not a byte string")); + } + } + + bool as_bool() const + { + switch (storage_kind()) + { + case json_storage_kind::boolean: + return cast().value(); + case json_storage_kind::int64: + return cast().value() != 0; + case json_storage_kind::uint64: + return cast().value() != 0; + case json_storage_kind::const_json_pointer: + return cast().value()->as_bool(); + default: + JSONCONS_THROW(json_runtime_error("Not a bool")); + } + } + + template + IntegerType as_integer() const + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + { + IntegerType val; + auto result = jsoncons::detail::to_integer(as_string_view().data(), as_string_view().length(), val); + if (!result) + { + JSONCONS_THROW(json_runtime_error(result.error_code().message())); + } + return val; + } + case json_storage_kind::half_float: + return static_cast(cast().value()); + case json_storage_kind::float64: + return static_cast(cast().value()); + case json_storage_kind::int64: + return static_cast(cast().value()); + case json_storage_kind::uint64: + return static_cast(cast().value()); + case json_storage_kind::boolean: + return static_cast(cast().value() ? 1 : 0); + case json_storage_kind::const_json_pointer: + return cast().value()->template as_integer(); + default: + JSONCONS_THROW(json_runtime_error("Not an integer")); + } + } + + template + typename std::enable_if::value && sizeof(IntegerType) <= sizeof(int64_t),bool>::type + is_integer() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::int64: + return (as_integer() >= (extension_traits::integer_limits::lowest)()) && (as_integer() <= (extension_traits::integer_limits::max)()); + case json_storage_kind::uint64: + return as_integer() <= static_cast((extension_traits::integer_limits::max)()); + case json_storage_kind::const_json_pointer: + return cast().value()->template is_integer(); + default: + return false; + } + } + + template + typename std::enable_if::value && sizeof(int64_t) < sizeof(IntegerType),bool>::type + is_integer() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + { + IntegerType val; + auto result = jsoncons::detail::to_integer(as_string_view().data(), as_string_view().length(), val); + return result ? true : false; + } + case json_storage_kind::int64: + return (as_integer() >= (extension_traits::integer_limits::lowest)()) && (as_integer() <= (extension_traits::integer_limits::max)()); + case json_storage_kind::uint64: + return as_integer() <= static_cast((extension_traits::integer_limits::max)()); + case json_storage_kind::const_json_pointer: + return cast().value()->template is_integer(); + default: + return false; + } + } + + template + typename std::enable_if::value && sizeof(IntegerType) <= sizeof(int64_t),bool>::type + is_integer() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::int64: + return as_integer() >= 0 && static_cast(as_integer()) <= (extension_traits::integer_limits::max)(); + case json_storage_kind::uint64: + return as_integer() <= (extension_traits::integer_limits::max)(); + case json_storage_kind::const_json_pointer: + return cast().value()->template is_integer(); + default: + return false; + } + } + + template + typename std::enable_if::value && sizeof(int64_t) < sizeof(IntegerType),bool>::type + is_integer() const noexcept + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + { + IntegerType val; + auto result = jsoncons::detail::to_integer(as_string_view().data(), as_string_view().length(), val); + return result ? true : false; + } + case json_storage_kind::int64: + return as_integer() >= 0 && static_cast(as_integer()) <= (extension_traits::integer_limits::max)(); + case json_storage_kind::uint64: + return as_integer() <= (extension_traits::integer_limits::max)(); + case json_storage_kind::const_json_pointer: + return cast().value()->template is_integer(); + default: + return false; + } + } + + double as_double() const + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + { + jsoncons::detail::chars_to to_double; + // to_double() throws std::invalid_argument if conversion fails + return to_double(as_cstring(), as_string_view().length()); + } + case json_storage_kind::half_float: + return binary::decode_half(cast().value()); + case json_storage_kind::float64: + return cast().value(); + case json_storage_kind::int64: + return static_cast(cast().value()); + case json_storage_kind::uint64: + return static_cast(cast().value()); + case json_storage_kind::const_json_pointer: + return cast().value()->as_double(); + default: + JSONCONS_THROW(json_runtime_error("Not a double")); + } + } + + template > + std::basic_string as_string() const + { + return as_string(SAllocator()); + } + + template > + std::basic_string as_string(const SAllocator& alloc) const + { + using string_type2 = std::basic_string; + + std::error_code ec; + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + { + return string_type2(as_string_view().data(),as_string_view().length(),alloc); + } + case json_storage_kind::byte_str: + { + value_converter converter; + auto s = converter.convert(as_byte_string_view(), tag(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + return s; + } + case json_storage_kind::array: + { + string_type2 s(alloc); + { + basic_compact_json_encoder> encoder(s); + dump(encoder); + } + return s; + } + case json_storage_kind::const_json_pointer: + return cast().value()->as_string(alloc); + default: + { + string_type2 s(alloc); + basic_compact_json_encoder> encoder(s); + dump(encoder); + return s; + } + } + } + + const char_type* as_cstring() const + { + switch (storage_kind()) + { + case json_storage_kind::short_str: + return cast().c_str(); + case json_storage_kind::long_str: + return cast().c_str(); + case json_storage_kind::const_json_pointer: + return cast().value()->as_cstring(); + default: + JSONCONS_THROW(json_runtime_error("Not a cstring")); + } + } + + basic_json& at(const string_view_type& name) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + JSONCONS_THROW(key_not_found(name.data(),name.length())); + case json_storage_kind::object: + { + auto it = cast().value().find(name); + if (it == cast().value().end()) + { + JSONCONS_THROW(key_not_found(name.data(),name.length())); + } + return it->value(); + } + default: + { + JSONCONS_THROW(not_an_object(name.data(),name.length())); + } + } + } + + const basic_json& at(const string_view_type& key) const + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + JSONCONS_THROW(key_not_found(key.data(),key.length())); + case json_storage_kind::object: + { + auto it = cast().value().find(key); + if (it == cast().value().end()) + { + JSONCONS_THROW(key_not_found(key.data(),key.length())); + } + return it->value(); + } + case json_storage_kind::const_json_pointer: + return cast().value()->at(key); + default: + { + JSONCONS_THROW(not_an_object(key.data(),key.length())); + } + } + } + + basic_json& at(std::size_t i) + { + switch (storage_kind()) + { + case json_storage_kind::array: + if (i >= cast().value().size()) + { + JSONCONS_THROW(json_runtime_error("Invalid array subscript")); + } + return cast().value().operator[](i); + case json_storage_kind::object: + return cast().value().at(i); + default: + JSONCONS_THROW(json_runtime_error("Index on non-array value not supported")); + } + } + + const basic_json& at(std::size_t i) const + { + switch (storage_kind()) + { + case json_storage_kind::array: + if (i >= cast().value().size()) + { + JSONCONS_THROW(json_runtime_error("Invalid array subscript")); + } + return cast().value().operator[](i); + case json_storage_kind::object: + return cast().value().at(i); + case json_storage_kind::const_json_pointer: + return cast().value()->at(i); + default: + JSONCONS_THROW(json_runtime_error("Index on non-array value not supported")); + } + } + + object_iterator find(const string_view_type& name) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + return object_range().end(); + case json_storage_kind::object: + return object_iterator(cast().value().find(name)); + default: + { + JSONCONS_THROW(not_an_object(name.data(),name.length())); + } + } + } + + const_object_iterator find(const string_view_type& key) const + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + return object_range().end(); + case json_storage_kind::object: + return const_object_iterator(cast().value().find(key)); + case json_storage_kind::const_json_pointer: + return cast().value()->find(key); + default: + { + JSONCONS_THROW(not_an_object(key.data(),key.length())); + } + } + } + + const basic_json& at_or_null(const string_view_type& key) const + { + switch (storage_kind()) + { + case json_storage_kind::null: + case json_storage_kind::empty_object: + { + return null(); + } + case json_storage_kind::object: + { + auto it = cast().value().find(key); + if (it != cast().value().end()) + { + return it->value(); + } + else + { + return null(); + } + } + case json_storage_kind::const_json_pointer: + return cast().value()->at_or_null(key); + default: + { + JSONCONS_THROW(not_an_object(key.data(),key.length())); + } + } + } + + template + T get_value_or(const string_view_type& key, U&& default_value) const + { + static_assert(std::is_copy_constructible::value, + "get_value_or: T must be copy constructible"); + static_assert(std::is_convertible::value, + "get_value_or: U must be convertible to T"); + switch (storage_kind()) + { + case json_storage_kind::null: + case json_storage_kind::empty_object: + return static_cast(std::forward(default_value)); + case json_storage_kind::object: + { + auto it = cast().value().find(key); + if (it != cast().value().end()) + { + return it->value().template as(); + } + else + { + return static_cast(std::forward(default_value)); + } + } + case json_storage_kind::const_json_pointer: + return cast().value()->template get_value_or(key,std::forward(default_value)); + default: + { + JSONCONS_THROW(not_an_object(key.data(),key.length())); + } + } + } + + // Modifiers + + void shrink_to_fit() + { + switch (storage_kind()) + { + case json_storage_kind::array: + cast().value().shrink_to_fit(); + break; + case json_storage_kind::object: + cast().value().shrink_to_fit(); + break; + default: + break; + } + } + + void clear() + { + switch (storage_kind()) + { + case json_storage_kind::array: + cast().value().clear(); + break; + case json_storage_kind::object: + cast().value().clear(); + break; + default: + break; + } + } + + object_iterator erase(const_object_iterator pos) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + return object_range().end(); + case json_storage_kind::object: + return object_iterator(cast().value().erase(pos)); + default: + JSONCONS_THROW(json_runtime_error("Not an object")); + break; + } + } + + object_iterator erase(const_object_iterator first, const_object_iterator last) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + return object_range().end(); + case json_storage_kind::object: + return object_iterator(cast().value().erase(first, last)); + default: + JSONCONS_THROW(json_runtime_error("Not an object")); + break; + } + } + + array_iterator erase(const_array_iterator pos) + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().erase(pos); + default: + JSONCONS_THROW(json_runtime_error("Not an array")); + } + } + + array_iterator erase(const_array_iterator first, const_array_iterator last) + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().erase(first, last); + default: + JSONCONS_THROW(json_runtime_error("Not an array")); + break; + } + } + + // Removes all elements from an array value whose index is between from_index, inclusive, and to_index, exclusive. + + void erase(const string_view_type& name) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + cast().value().erase(name); + break; + default: + JSONCONS_THROW(not_an_object(name.data(),name.length())); + break; + } + } + + template + std::pair insert_or_assign(const string_view_type& name, T&& val) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + { + create_object_implicitly(); + auto result = cast().value().insert_or_assign(name, std::forward(val)); + return std::make_pair(object_iterator(result.first), result.second); + } + case json_storage_kind::object: + { + auto result = cast().value().insert_or_assign(name, std::forward(val)); + return std::make_pair(object_iterator(result.first), result.second); + } + default: + JSONCONS_THROW(not_an_object(name.data(),name.length())); + } + } + + template + std::pair try_emplace(const string_view_type& name, Args&&... args) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + { + create_object_implicitly(); + auto result = cast().value().try_emplace(name, std::forward(args)...); + return std::make_pair(object_iterator(result.first),result.second); + } + case json_storage_kind::object: + { + auto result = cast().value().try_emplace(name, std::forward(args)...); + return std::make_pair(object_iterator(result.first),result.second); + } + default: + JSONCONS_THROW(not_an_object(name.data(),name.length())); + } + } + + // merge + + void merge(const basic_json& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge(source.cast().value()); + break; + case json_storage_kind::object: + cast().value().merge(source.cast().value()); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + void merge(basic_json&& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge(std::move(source.cast().value())); + break; + case json_storage_kind::object: + cast().value().merge(std::move(source.cast().value())); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + void merge(object_iterator hint, const basic_json& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge(hint, source.cast().value()); + break; + case json_storage_kind::object: + cast().value().merge(hint, source.cast().value()); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + void merge(object_iterator hint, basic_json&& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge(hint, std::move(source.cast().value())); + break; + case json_storage_kind::object: + cast().value().merge(hint, std::move(source.cast().value())); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + // merge_or_update + + void merge_or_update(const basic_json& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge_or_update(source.cast().value()); + break; + case json_storage_kind::object: + cast().value().merge_or_update(source.cast().value()); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge or update a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + void merge_or_update(basic_json&& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge_or_update(std::move(source.cast().value())); + break; + case json_storage_kind::object: + cast().value().merge_or_update(std::move(source.cast().value())); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge or update a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + void merge_or_update(object_iterator hint, const basic_json& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge_or_update(hint, source.cast().value()); + break; + case json_storage_kind::object: + cast().value().merge_or_update(hint, source.cast().value()); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge or update a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + void merge_or_update(object_iterator hint, basic_json&& source) + { + switch (source.storage_kind()) + { + case json_storage_kind::empty_object: + break; + case json_storage_kind::object: + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().merge_or_update(hint, std::move(source.cast().value())); + break; + case json_storage_kind::object: + cast().value().merge_or_update(hint, std::move(source.cast().value())); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge or update a value that is not an object")); + } + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to merge a value that is not an object")); + } + } + + template + object_iterator insert_or_assign(object_iterator hint, const string_view_type& name, T&& val) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + return object_iterator(cast().value().insert_or_assign(hint, name, std::forward(val))); + case json_storage_kind::object: + return object_iterator(cast().value().insert_or_assign(hint, name, std::forward(val))); + default: + JSONCONS_THROW(not_an_object(name.data(),name.length())); + } + } + + template + object_iterator try_emplace(object_iterator hint, const string_view_type& name, Args&&... args) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + return object_iterator(cast().value().try_emplace(hint, name, std::forward(args)...)); + case json_storage_kind::object: + return object_iterator(cast().value().try_emplace(hint, name, std::forward(args)...)); + default: + JSONCONS_THROW(not_an_object(name.data(),name.length())); + } + } + + template + array_iterator insert(const_array_iterator pos, T&& val) + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().insert(pos, std::forward(val)); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an array")); + } + } + + template + array_iterator insert(const_array_iterator pos, InputIt first, InputIt last) + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().insert(pos, first, last); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an array")); + } + } + + template + void insert(InputIt first, InputIt last) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().insert(first, last); + break; + case json_storage_kind::object: + cast().value().insert(first, last); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an object")); + } + } + + template + void insert(sorted_unique_range_tag tag, InputIt first, InputIt last) + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + create_object_implicitly(); + cast().value().insert(tag, first, last); + break; + case json_storage_kind::object: + cast().value().insert(tag, first, last); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an object")); + } + } + + template + array_iterator emplace(const_array_iterator pos, Args&&... args) + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().emplace(pos, std::forward(args)...); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an array")); + } + } + + template + basic_json& emplace_back(Args&&... args) + { + switch (storage_kind()) + { + case json_storage_kind::array: + return cast().value().emplace_back(std::forward(args)...); + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an array")); + } + } + + friend void swap(basic_json& a, basic_json& b) noexcept + { + a.swap(b); + } + + template + void push_back(T&& val) + { + switch (storage_kind()) + { + case json_storage_kind::array: + cast().value().push_back(std::forward(val)); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an array")); + } + } + + void push_back(basic_json&& val) + { + switch (storage_kind()) + { + case json_storage_kind::array: + cast().value().push_back(std::move(val)); + break; + default: + JSONCONS_THROW(json_runtime_error("Attempting to insert into a value that is not an array")); + } + } + + std::basic_string to_string() const noexcept + { + using string_type2 = std::basic_string; + string_type2 s; + basic_compact_json_encoder> encoder(s); + dump(encoder); + return s; + } + + template + basic_json(InputIterator first, InputIterator last, const Allocator& alloc = Allocator()) + : basic_json(json_array_arg,first,last,alloc) + { + } + + object_range_type object_range() + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + return object_range_type(object_iterator(), object_iterator()); + case json_storage_kind::object: + return object_range_type(object_iterator(cast().value().begin()), + object_iterator(cast().value().end())); + default: + JSONCONS_THROW(json_runtime_error("Not an object")); + } + } + + const_object_range_type object_range() const + { + switch (storage_kind()) + { + case json_storage_kind::empty_object: + return const_object_range_type(const_object_iterator(), const_object_iterator()); + case json_storage_kind::object: + return const_object_range_type(const_object_iterator(cast().value().begin()), + const_object_iterator(cast().value().end())); + case json_storage_kind::const_json_pointer: + return cast().value()->object_range(); + default: + JSONCONS_THROW(json_runtime_error("Not an object")); + } + } + + array_range_type array_range() + { + switch (storage_kind()) + { + case json_storage_kind::array: + return array_range_type(cast().value().begin(), + cast().value().end()); + default: + JSONCONS_THROW(json_runtime_error("Not an array")); + } + } + + const_array_range_type array_range() const + { + switch (storage_kind()) + { + case json_storage_kind::array: + return const_array_range_type(cast().value().begin(), + cast().value().end()); + case json_storage_kind::const_json_pointer: + return cast().value()->array_range(); + default: + JSONCONS_THROW(json_runtime_error("Not an array")); + } + } + + private: + + void dump_noflush(basic_json_visitor& visitor, std::error_code& ec) const + { + const ser_context context{}; + switch (storage_kind()) + { + case json_storage_kind::short_str: + case json_storage_kind::long_str: + visitor.string_value(as_string_view(), tag(), context, ec); + break; + case json_storage_kind::byte_str: + if (tag() == semantic_tag::ext) + { + visitor.byte_string_value(as_byte_string_view(), ext_tag(), context, ec); + } + else + { + visitor.byte_string_value(as_byte_string_view(), tag(), context, ec); + } + break; + case json_storage_kind::half_float: + visitor.half_value(cast().value(), tag(), context, ec); + break; + case json_storage_kind::float64: + visitor.double_value(cast().value(), + tag(), context, ec); + break; + case json_storage_kind::int64: + visitor.int64_value(cast().value(), tag(), context, ec); + break; + case json_storage_kind::uint64: + visitor.uint64_value(cast().value(), tag(), context, ec); + break; + case json_storage_kind::boolean: + visitor.bool_value(cast().value(), tag(), context, ec); + break; + case json_storage_kind::null: + visitor.null_value(tag(), context, ec); + break; + case json_storage_kind::empty_object: + visitor.begin_object(0, tag(), context, ec); + visitor.end_object(context, ec); + break; + case json_storage_kind::object: + { + bool more = visitor.begin_object(size(), tag(), context, ec); + const object& o = cast().value(); + for (auto it = o.begin(); more && it != o.end(); ++it) + { + visitor.key(string_view_type((it->key()).data(),it->key().length()), context, ec); + it->value().dump_noflush(visitor, ec); + } + if (more) + { + visitor.end_object(context, ec); + } + break; + } + case json_storage_kind::array: + { + bool more = visitor.begin_array(size(), tag(), context, ec); + const array& o = cast().value(); + for (const_array_iterator it = o.begin(); more && it != o.end(); ++it) + { + it->dump_noflush(visitor, ec); + } + if (more) + { + visitor.end_array(context, ec); + } + break; + } + case json_storage_kind::const_json_pointer: + return cast().value()->dump_noflush(visitor, ec); + default: + break; + } + } + + friend std::basic_ostream& operator<<(std::basic_ostream& os, const basic_json& o) + { + o.dump(os); + return os; + } + + friend std::basic_istream& operator>>(std::basic_istream& is, basic_json& o) + { + json_decoder visitor; + basic_json_reader> reader(is, visitor); + reader.read_next(); + reader.check_done(); + if (!visitor.is_valid()) + { + JSONCONS_THROW(json_runtime_error("Failed to parse json stream")); + } + o = visitor.get_result(); + return is; + } + + friend basic_json deep_copy(const basic_json& other) + { + switch (other.storage_kind()) + { + case json_storage_kind::array: + { + basic_json j(json_array_arg, other.tag(), other.get_allocator()); + j.reserve(other.size()); + + for (const auto& item : other.array_range()) + { + j.push_back(deep_copy(item)); + } + return j; + } + case json_storage_kind::object: + { + basic_json j(json_object_arg, other.tag(), other.get_allocator()); + j.reserve(other.size()); + + for (const auto& item : other.object_range()) + { + j.try_emplace(item.key(), deep_copy(item.value())); + } + return j; + } + case json_storage_kind::const_json_pointer: + return deep_copy(*(other.cast().value())); + default: + return other; + } + } + }; + + // operator== + + template + typename std::enable_if::value,bool>::type + operator==(const Json& lhs, const Json& rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator==(const Json& lhs, const T& rhs) + { + return lhs.compare(rhs) == 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator==(const Json& lhs, const T& rhs) + { + return lhs.evaluate().compare(rhs) == 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator==(const T& lhs, const Json& rhs) + { + return rhs.compare(lhs) == 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator==(const T& lhs, const Json& rhs) + { + return rhs.evaluate().compare(lhs) == 0; + } + + // operator!= + + template + typename std::enable_if::value,bool>::type + operator!=(const Json& lhs, const Json& rhs) noexcept + { + return lhs.compare(rhs) != 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator!=(const Json& lhs, const T& rhs) + { + return lhs.compare(rhs) != 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator!=(const Json& lhs, const T& rhs) + { + return lhs.evaluate().compare(rhs) != 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator!=(const T& lhs, const Json& rhs) + { + return rhs.compare(lhs) != 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator!=(const T& lhs, const Json& rhs) + { + return rhs.evaluate().compare(lhs) != 0; + } + + // operator< + + template + typename std::enable_if::value,bool>::type + operator<(const Json& lhs, const Json& rhs) noexcept + { + return lhs.compare(rhs) < 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator<(const Json& lhs, const T& rhs) + { + return lhs.compare(rhs) < 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator<(const Json& lhs, const T& rhs) + { + return lhs.evaluate().compare(rhs) < 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator<(const T& lhs, const Json& rhs) + { + return rhs.compare(lhs) > 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator<(const T& lhs, const Json& rhs) + { + return rhs.evaluate().compare(lhs) > 0; + } + + // operator<= + + template + typename std::enable_if::value,bool>::type + operator<=(const Json& lhs, const Json& rhs) noexcept + { + return lhs.compare(rhs) <= 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator<=(const Json& lhs, const T& rhs) + { + return lhs.compare(rhs) <= 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator<=(const Json& lhs, const T& rhs) + { + return lhs.evaluate().compare(rhs) <= 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator<=(const T& lhs, const Json& rhs) + { + return rhs.compare(lhs) >= 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator<=(const T& lhs, const Json& rhs) + { + return rhs.evaluate().compare(lhs) >= 0; + } + + // operator> + + template + typename std::enable_if::value,bool>::type + operator>(const Json& lhs, const Json& rhs) noexcept + { + return lhs.compare(rhs) > 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator>(const Json& lhs, const T& rhs) + { + return lhs.compare(rhs) > 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator>(const Json& lhs, const T& rhs) + { + return lhs.evaluate().compare(rhs) > 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator>(const T& lhs, const Json& rhs) + { + return rhs.compare(lhs) < 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator>(const T& lhs, const Json& rhs) + { + return rhs.evaluate().compare(lhs) < 0; + } + + // operator>= + + template + typename std::enable_if::value,bool>::type + operator>=(const Json& lhs, const Json& rhs) noexcept + { + return lhs.compare(rhs) >= 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator>=(const Json& lhs, const T& rhs) + { + return lhs.compare(rhs) >= 0; + } + + template + typename std::enable_if::value && std::is_convertible::value,bool>::type + operator>=(const Json& lhs, const T& rhs) + { + return lhs.evaluate().compare(rhs) >= 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator>=(const T& lhs, const Json& rhs) + { + return rhs.compare(lhs) <= 0; + } + + template + typename std::enable_if::value && !is_proxy::value && !extension_traits::is_basic_json::value && std::is_convertible::value,bool>::type + operator>=(const T& lhs, const Json& rhs) + { + return rhs.evaluate().compare(lhs) <= 0; + } + + // swap + + template + void swap(typename Json::key_value_type& a,typename Json::key_value_type& b) noexcept + { + a.swap(b); + } + + using json = basic_json>; + using wjson = basic_json>; + using ojson = basic_json>; + using wojson = basic_json>; + + inline namespace literals { + + inline + jsoncons::json operator "" _json(const char* s, std::size_t n) + { + return jsoncons::json::parse(jsoncons::json::string_view_type(s, n)); + } + + inline + jsoncons::wjson operator "" _json(const wchar_t* s, std::size_t n) + { + return jsoncons::wjson::parse(jsoncons::wjson::string_view_type(s, n)); + } + + inline + jsoncons::ojson operator "" _ojson(const char* s, std::size_t n) + { + return jsoncons::ojson::parse(jsoncons::ojson::string_view_type(s, n)); + } + + inline + jsoncons::wojson operator "" _ojson(const wchar_t* s, std::size_t n) + { + return jsoncons::wojson::parse(jsoncons::wojson::string_view_type(s, n)); + } + + } // inline namespace literals + + #if defined(JSONCONS_HAS_POLYMORPHIC_ALLOCATOR) + namespace pmr { + template< typename CharT,typename Policy> + using basic_json = jsoncons::basic_json>; + using json = basic_json; + using wjson = basic_json; + using ojson = basic_json; + using wojson = basic_json; + } // namespace pmr + #endif + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/bigint.hpp b/third_party/jsoncons/bigint.hpp new file mode 100644 index 0000000000..874d166495 --- /dev/null +++ b/third_party/jsoncons/bigint.hpp @@ -0,0 +1,1632 @@ +// Copyright 2018 vDaniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BIGINT_HPP +#define JSONCONS_BIGINT_HPP + +#include +#include // std::vector +#include +#include +#include // assert +#include // std::numeric_limits +#include // std::max, std::min, std::reverse +#include // std::string +#include // std::memcpy +#include // std::fmod +#include // std::allocator +#include // std::initializer_list +#include // std::enable_if +#include + +namespace jsoncons { + +/* +This implementation is based on Chapter 2 and Appendix A of +Ammeraal, L. (1996) Algorithms and Data Structures in C++, +Chichester: John Wiley. + +*/ + + +namespace detail { + + template + class basic_bigint_base + { + public: + using allocator_type = Allocator; + using basic_type_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + private: + basic_type_allocator_type alloc_; + public: + using allocator_traits_type = std::allocator_traits; + using stored_allocator_type = allocator_type; + using pointer = typename allocator_traits_type::pointer; + using value_type = typename allocator_traits_type::value_type; + using size_type = std::size_t; + using pointer_traits = std::pointer_traits; + + basic_bigint_base() + : alloc_() + { + } + explicit basic_bigint_base(const allocator_type& alloc) + : alloc_(basic_type_allocator_type(alloc)) + { + } + + basic_type_allocator_type get_allocator() const + { + return alloc_; + } + }; + +} // namespace detail + +template > +class basic_bigint : protected detail::basic_bigint_base +{ + using base_t = detail::basic_bigint_base; + + static constexpr uint64_t max_short_storage_size = 2; +public: + + using size_type = typename base_t::size_type; + using value_type = typename base_t::value_type; + using base_t::get_allocator; + using bigint_type = basic_bigint; + + static constexpr uint64_t max_basic_type = (std::numeric_limits::max)(); + static constexpr uint64_t basic_type_bits = sizeof(uint64_t) * 8; // Number of bits + static constexpr uint64_t basic_type_halfBits = basic_type_bits/2; + + static constexpr uint16_t word_length = 4; // Use multiples of word_length words + static constexpr uint64_t r_mask = (uint64_t(1) << basic_type_halfBits) - 1; + static constexpr uint64_t l_mask = max_basic_type - r_mask; + static constexpr uint64_t l_bit = max_basic_type - (max_basic_type >> 1); + +private: + + struct common_storage + { + uint8_t is_dynamic_:1; + uint8_t is_negative_:1; + size_type length_; + }; + + struct short_storage + { + uint8_t is_dynamic_:1; + uint8_t is_negative_:1; + size_type length_; + uint64_t values_[max_short_storage_size]; + + short_storage() + : is_dynamic_(false), + is_negative_(false), + length_(0), + values_{0,0} + { + } + + template + short_storage(T n, + typename std::enable_if::value && + sizeof(T) <= sizeof(int64_t) && + std::is_signed::value>::type* = 0) + : is_dynamic_(false), + is_negative_(n < 0), + length_(n == 0 ? 0 : 1) + { + values_[0] = n < 0 ? (uint64_t(0)-static_cast(n)) : static_cast(n); + values_[1] = 0; + } + + template + short_storage(T n, + typename std::enable_if::value && + sizeof(T) <= sizeof(int64_t) && + !std::is_signed::value>::type* = 0) + : is_dynamic_(false), + is_negative_(false), + length_(n == 0 ? 0 : 1) + { + values_[0] = n; + values_[1] = 0; + } + + template + short_storage(T n, + typename std::enable_if::value && + sizeof(int64_t) < sizeof(T) && + std::is_signed::value>::type* = 0) + : is_dynamic_(false), + is_negative_(n < 0), + length_(n == 0 ? 0 : max_short_storage_size) + { + using unsigned_type = typename std::make_unsigned::type; + + auto u = n < 0 ? (unsigned_type(0)-static_cast(n)) : static_cast(n); + values_[0] = uint64_t(u & max_basic_type);; + u >>= basic_type_bits; + values_[1] = uint64_t(u & max_basic_type);; + } + + template + short_storage(T n, + typename std::enable_if::value && + sizeof(int64_t) < sizeof(T) && + !std::is_signed::value>::type* = 0) + : is_dynamic_(false), + is_negative_(false), + length_(n == 0 ? 0 : max_short_storage_size) + { + values_[0] = uint64_t(n & max_basic_type);; + n >>= basic_type_bits; + values_[1] = uint64_t(n & max_basic_type);; + } + + short_storage(const short_storage& stor) + : is_dynamic_(false), + is_negative_(stor.is_negative_), + length_(stor.length_) + { + values_[0] = stor.values_[0]; + values_[1] = stor.values_[1]; + } + + short_storage& operator=(const short_storage& stor) = delete; + }; + + struct dynamic_storage + { + using real_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using pointer = typename std::allocator_traits::pointer; + + uint8_t is_dynamic_:1; + uint8_t is_negative_:1; + size_type length_; + size_type capacity_; + pointer data_; + + dynamic_storage() + : is_dynamic_(true), + is_negative_(false), + length_(0), + capacity_(0), + data_(nullptr) + { + } + + dynamic_storage(const dynamic_storage& stor, const real_allocator_type& alloc) + : is_dynamic_(true), + is_negative_(stor.is_negative_), + length_(stor.length_), + capacity_(0), + data_(nullptr) + { + create(stor.length_, alloc); + std::memcpy(data_, stor.data_, size_type(stor.length_*sizeof(uint64_t))); + } + + dynamic_storage(dynamic_storage&& stor) noexcept + : is_dynamic_(true), + is_negative_(stor.is_negative_), + length_(stor.length_), + capacity_(stor.capacity_), + data_(stor.data_) + { + stor.length_ = 0; + stor.capacity_ = 0; + stor.data_ = nullptr; + } + + void create(size_type length, real_allocator_type alloc) + { + capacity_ = round_up(length); + data_ = std::allocator_traits::allocate(alloc, capacity_); + JSONCONS_TRY + { + std::allocator_traits::construct(alloc, extension_traits::to_plain_pointer(data_)); + } + JSONCONS_CATCH(...) + { + std::allocator_traits::deallocate(alloc, data_, capacity_); + JSONCONS_RETHROW; + } + } + + void destroy(const real_allocator_type& a) noexcept + { + if (data_ != nullptr) + { + real_allocator_type alloc(a); + + std::allocator_traits::destroy(alloc, extension_traits::to_plain_pointer(data_)); + std::allocator_traits::deallocate(alloc, data_,capacity_); + } + } + + void reserve(size_type n, const real_allocator_type& a) + { + real_allocator_type alloc(a); + + size_type capacity_new = round_up(n); + uint64_t* data_old = data_; + data_ = std::allocator_traits::allocate(alloc, capacity_new); + if (length_ > 0) + { + std::memcpy( data_, data_old, size_type(length_*sizeof(uint64_t))); + } + if (capacity_ > 0 && data_ != nullptr) + { + std::allocator_traits::deallocate(alloc, data_old, capacity_); + } + capacity_ = capacity_new; + } + + // Find suitable new block size + constexpr size_type round_up(size_type i) const + { + return (i/word_length + 1) * word_length; + } + }; + + union + { + common_storage common_stor_; + short_storage short_stor_; + dynamic_storage dynamic_stor_; + }; + +public: + basic_bigint() + { + ::new (&short_stor_) short_storage(); + } + + explicit basic_bigint(const Allocator& alloc) + : base_t(alloc) + { + ::new (&short_stor_) short_storage(); + } + + + basic_bigint(const basic_bigint& n) + : base_t(n.get_allocator()) + { + if (!n.is_dynamic()) + { + ::new (&short_stor_) short_storage(n.short_stor_); + } + else + { + ::new (&dynamic_stor_) dynamic_storage(n.dynamic_stor_, get_allocator()); + } + } + + basic_bigint(basic_bigint&& other) noexcept + : base_t(other.get_allocator()) + { + if (!other.is_dynamic()) + { + ::new (&short_stor_) short_storage(other.short_stor_); + } + else + { + ::new (&dynamic_stor_) dynamic_storage(std::move(other.dynamic_stor_)); + } + } + + template + basic_bigint(Integer n, + typename std::enable_if::value>::type* = 0) + { + ::new (&short_stor_) short_storage(n); + } + + ~basic_bigint() noexcept + { + destroy(); + } + + constexpr bool is_dynamic() const + { + return common_stor_.is_dynamic_; + } + + constexpr size_type length() const + { + return common_stor_.length_; + } + + constexpr size_type capacity() const + { + return is_dynamic() ? dynamic_stor_.capacity_ : max_short_storage_size; + } + + bool is_negative() const + { + return common_stor_.is_negative_; + } + + void is_negative(bool value) + { + common_stor_.is_negative_ = value; + } + + constexpr const uint64_t* data() const + { + return is_dynamic() ? dynamic_stor_.data_ : short_stor_.values_; + } + + uint64_t* data() + { + return is_dynamic() ? dynamic_stor_.data_ : short_stor_.values_; + } + + template + static basic_bigint from_string(const std::basic_string& s) + { + return from_string(s.data(), s.length()); + } + + template + static basic_bigint from_string(const CharT* s) + { + return from_string(s, std::char_traits::length(s)); + } + + template + static basic_bigint from_string(const CharT* data, size_type length) + { + bool neg; + if (*data == '-') + { + neg = true; + data++; + --length; + } + else + { + neg = false; + } + + basic_bigint v = 0; + for (size_type i = 0; i < length; i++) + { + CharT c = data[i]; + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + v = (v * 10) + (uint64_t)(c - '0'); + break; + default: + JSONCONS_THROW(std::runtime_error(std::string("Invalid digit ") + "\'" + (char)c + "\'")); + } + } + + if (neg) + { + v.common_stor_.is_negative_ = true; + } + + return v; + } + + template + static basic_bigint from_string_radix(const CharT* data, size_type length, uint8_t radix) + { + if (!(radix >= 2 && radix <= 16)) + { + JSONCONS_THROW(std::runtime_error("Unsupported radix")); + } + + bool neg; + if (*data == '-') + { + neg = true; + data++; + --length; + } + else + { + neg = false; + } + + basic_bigint v = 0; + for (size_type i = 0; i < length; i++) + { + CharT c = data[i]; + uint64_t d; + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + d = (uint64_t)(c - '0'); + break; + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + d = (uint64_t)(c - ('a' - 10)); + break; + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + d = (uint64_t)(c - ('A' - 10)); + break; + default: + JSONCONS_THROW(std::runtime_error(std::string("Invalid digit in radix ") + std::to_string(radix) + ": \'" + (char)c + "\'")); + } + if (d >= radix) + { + JSONCONS_THROW(std::runtime_error(std::string("Invalid digit in radix ") + std::to_string(radix) + ": \'" + (char)c + "\'")); + } + v = (v * radix) + d; + } + + if ( neg ) + { + v.common_stor_.is_negative_ = true; + } + return v; + } + + static basic_bigint from_bytes_be(int signum, const uint8_t* str, std::size_t n) + { + static const double radix_log2 = std::log2(next_power_of_two(256)); + // Estimate how big the result will be, so we can pre-allocate it. + double bits = radix_log2 * n; + double big_digits = std::ceil(bits / 64.0); + //std::cout << "ESTIMATED: " << big_digits << "\n"; + + bigint_type v = 0; + v.reserve(static_cast(big_digits)); + + if (n > 0) + { + for (std::size_t i = 0; i < n; i++) + { + v = (v * 256) + (uint64_t)(str[i]); + } + } + //std::cout << "ACTUAL: " << v.length() << "\n"; + + if (signum < 0) + { + v.common_stor_.is_negative_ = true; + } + + return v; + } + + uint64_t* begin() { return is_dynamic() ? dynamic_stor_.data_ : short_stor_.values_; } + const uint64_t* begin() const { return is_dynamic() ? dynamic_stor_.data_ : short_stor_.values_; } + uint64_t* end() { return begin() + length(); } + const uint64_t* end() const { return begin() + length(); } + + void resize(size_type new_length) + { + size_type old_length = common_stor_.length_; + reserve(new_length); + common_stor_.length_ = new_length; + + if (old_length < new_length) + { + if (is_dynamic()) + { + std::memset(dynamic_stor_.data_+old_length, 0, size_type((new_length-old_length)*sizeof(uint64_t))); + } + else + { + JSONCONS_ASSERT(new_length <= max_short_storage_size); + for (size_type i = old_length; i < max_short_storage_size; ++i) + { + short_stor_.values_[i] = 0; + } + } + } + } + + void reserve(size_type n) + { + if (capacity() < n) + { + if (!is_dynamic()) + { + size_type size = short_stor_.length_; + size_type is_neg = short_stor_.is_negative_; + uint64_t values[max_short_storage_size] = {short_stor_.values_[0], short_stor_.values_[1]}; + + ::new (&dynamic_stor_) dynamic_storage(); + dynamic_stor_.reserve(n, get_allocator()); + dynamic_stor_.length_ = size; + dynamic_stor_.is_negative_ = is_neg; + dynamic_stor_.data_[0] = values[0]; + dynamic_stor_.data_[1] = values[1]; + } + else + { + dynamic_stor_.reserve(n, get_allocator()); + } + } + } + + // operators + + bool operator!() const + { + return length() == 0 ? true : false; + } + + basic_bigint operator-() const + { + basic_bigint v(*this); + v.common_stor_.is_negative_ = !v.is_negative(); + return v; + } + + basic_bigint& operator=( const basic_bigint& y ) + { + if ( this != &y ) + { + resize( y.length() ); + common_stor_.is_negative_ = y.is_negative(); + if ( y.length() > 0 ) + { + std::memcpy( data(), y.data(), size_type(y.length()*sizeof(uint64_t)) ); + } + } + return *this; + } + + basic_bigint& operator+=( const basic_bigint& y ) + { + if ( is_negative() != y.is_negative() ) + return *this -= -y; + uint64_t d; + uint64_t carry = 0; + + resize( (std::max)(y.length(), length()) + 1 ); + + for (size_type i = 0; i < length(); i++ ) + { + if ( i >= y.length() && carry == 0 ) + break; + d = data()[i] + carry; + carry = d < carry; + if ( i < y.length() ) + { + data()[i] = d + y.data()[i]; + if ( data()[i] < d ) + carry = 1; + } + else + data()[i] = d; + } + reduce(); + return *this; + } + + basic_bigint& operator-=( const basic_bigint& y ) + { + if ( is_negative() != y.is_negative() ) + return *this += -y; + if ( (!is_negative() && y > *this) || (is_negative() && y < *this) ) + return *this = -(y - *this); + uint64_t borrow = 0; + uint64_t d; + for (size_type i = 0; i < length(); i++ ) + { + if ( i >= y.length() && borrow == 0 ) + break; + d = data()[i] - borrow; + borrow = d > data()[i]; + if ( i < y.length()) + { + data()[i] = d - y.data()[i]; + if ( data()[i] > d ) + borrow = 1; + } + else + data()[i] = d; + } + reduce(); + return *this; + } + + basic_bigint& operator*=( int64_t y ) + { + *this *= uint64_t(y < 0 ? -y : y); + if ( y < 0 ) + common_stor_.is_negative_ = !is_negative(); + return *this; + } + + basic_bigint& operator*=( uint64_t y ) + { + size_type len0 = length(); + uint64_t hi; + uint64_t lo; + uint64_t dig = data()[0]; + uint64_t carry = 0; + + resize( length() + 1 ); + + size_type i = 0; + for (i = 0; i < len0; i++ ) + { + DDproduct( dig, y, hi, lo ); + data()[i] = lo + carry; + dig = data()[i+1]; + carry = hi + (data()[i] < lo); + } + data()[i] = carry; + reduce(); + return *this; + } + + basic_bigint& operator*=( basic_bigint y ) + { + if ( length() == 0 || y.length() == 0 ) + return *this = 0; + bool difSigns = is_negative() != y.is_negative(); + if ( length() + y.length() == max_short_storage_size ) // length() = y.length() = 1 + { + uint64_t a = data()[0], b = y.data()[0]; + data()[0] = a * b; + if ( data()[0] / a != b ) + { + resize( max_short_storage_size ); + DDproduct( a, b, data()[1], data()[0] ); + } + common_stor_.is_negative_ = difSigns; + return *this; + } + if ( length() == 1 ) // && y.length() > 1 + { + uint64_t digit = data()[0]; + *this = y; + *this *= digit; + } + else + { + if ( y.length() == 1 ) + *this *= y.data()[0]; + else + { + size_type lenProd = length() + y.length(), jA, jB; + uint64_t sumHi = 0, sumLo, hi, lo, + sumLo_old, sumHi_old, carry=0; + basic_bigint x = *this; + resize( lenProd ); // Give *this length lenProd + + for (size_type i = 0; i < lenProd; i++ ) + { + sumLo = sumHi; + sumHi = carry; + carry = 0; + for ( jA=0; jA < x.length(); jA++ ) + { + jB = i - jA; + if ( jB >= 0 && jB < y.length() ) + { + DDproduct( x.data()[jA], y.data()[jB], hi, lo ); + sumLo_old = sumLo; + sumHi_old = sumHi; + sumLo += lo; + if ( sumLo < sumLo_old ) + sumHi++; + sumHi += hi; + carry += (sumHi < sumHi_old); + } + } + data()[i] = sumLo; + } + } + } + reduce(); + common_stor_.is_negative_ = difSigns; + return *this; + } + + basic_bigint& operator/=( const basic_bigint& divisor ) + { + basic_bigint r; + divide( divisor, *this, r, false ); + return *this; + } + + basic_bigint& operator%=( const basic_bigint& divisor ) + { + basic_bigint q; + divide( divisor, q, *this, true ); + return *this; + } + + basic_bigint& operator<<=( uint64_t k ) + { + size_type q = size_type(k / basic_type_bits); + if ( q ) // Increase common_stor_.length_ by q: + { + resize(length() + q); + for (size_type i = length(); i-- > 0; ) + data()[i] = ( i < q ? 0 : data()[i - q]); + k %= basic_type_bits; + } + if ( k ) // 0 < k < basic_type_bits: + { + uint64_t k1 = basic_type_bits - k; + uint64_t mask = (uint64_t(1) << k) - uint64_t(1); + resize( length() + 1 ); + for (size_type i = length(); i-- > 0; ) + { + data()[i] <<= k; + if ( i > 0 ) + data()[i] |= (data()[i-1] >> k1) & mask; + } + } + reduce(); + return *this; + } + + basic_bigint& operator>>=(uint64_t k) + { + size_type q = size_type(k / basic_type_bits); + if ( q >= length() ) + { + resize( 0 ); + return *this; + } + if (q > 0) + { + memmove( data(), data()+q, size_type((length() - q)*sizeof(uint64_t)) ); + resize( size_type(length() - q) ); + k %= basic_type_bits; + if ( k == 0 ) + { + reduce(); + return *this; + } + } + + size_type n = size_type(length() - 1); + int64_t k1 = basic_type_bits - k; + uint64_t mask = (uint64_t(1) << k) - 1; + for (size_type i = 0; i <= n; i++) + { + data()[i] >>= k; + if ( i < n ) + data()[i] |= ((data()[i+1] & mask) << k1); + } + reduce(); + return *this; + } + + basic_bigint& operator++() + { + *this += 1; + return *this; + } + + basic_bigint operator++(int) + { + basic_bigint old = *this; + ++(*this); + return old; + } + + basic_bigint& operator--() + { + *this -= 1; + return *this; + } + + basic_bigint operator--(int) + { + basic_bigint old = *this; + --(*this); + return old; + } + + basic_bigint& operator|=( const basic_bigint& a ) + { + if ( length() < a.length() ) + { + resize( a.length() ); + } + + const uint64_t* qBegin = a.begin(); + const uint64_t* q = a.end() - 1; + uint64_t* p = begin() + a.length() - 1; + + while ( q >= qBegin ) + { + *p-- |= *q--; + } + + reduce(); + + return *this; + } + + basic_bigint& operator^=( const basic_bigint& a ) + { + if ( length() < a.length() ) + { + resize( a.length() ); + } + + const uint64_t* qBegin = a.begin(); + const uint64_t* q = a.end() - 1; + uint64_t* p = begin() + a.length() - 1; + + while ( q >= qBegin ) + { + *p-- ^= *q--; + } + + reduce(); + + return *this; + } + + basic_bigint& operator&=( const basic_bigint& a ) + { + size_type old_length = length(); + + resize( (std::min)( length(), a.length() ) ); + + const uint64_t* pBegin = begin(); + uint64_t* p = end() - 1; + const uint64_t* q = a.begin() + length() - 1; + + while ( p >= pBegin ) + { + *p-- &= *q--; + } + + const size_type new_length = length(); + if ( old_length > new_length ) + { + if (is_dynamic()) + { + std::memset( dynamic_stor_.data_ + new_length, 0, size_type(old_length - new_length*sizeof(uint64_t)) ); + } + else + { + JSONCONS_ASSERT(new_length <= max_short_storage_size); + for (size_type i = new_length; i < max_short_storage_size; ++i) + { + short_stor_.values_[i] = 0; + } + } + } + + reduce(); + + return *this; + } + + explicit operator bool() const + { + return length() != 0 ? true : false; + } + + explicit operator int64_t() const + { + int64_t x = 0; + if ( length() > 0 ) + { + x = data() [0]; + } + + return is_negative() ? -x : x; + } + + explicit operator uint64_t() const + { + uint64_t u = 0; + if ( length() > 0 ) + { + u = data() [0]; + } + + return u; + } + + explicit operator double() const + { + double x = 0.0; + double factor = 1.0; + double values = (double)max_basic_type + 1.0; + + const uint64_t* p = begin(); + const uint64_t* pEnd = end(); + while ( p < pEnd ) + { + x += *p*factor; + factor *= values; + ++p; + } + + return is_negative() ? -x : x; + } + + explicit operator long double() const + { + long double x = 0.0; + long double factor = 1.0; + long double values = (long double)max_basic_type + 1.0; + + const uint64_t* p = begin(); + const uint64_t* pEnd = end(); + while ( p < pEnd ) + { + x += *p*factor; + factor *= values; + ++p; + } + + return is_negative() ? -x : x; + } + + template + void write_bytes_be(int& signum, std::vector& data) const + { + basic_bigint n(*this); + signum = (n < 0) ? -1 : (n > 0 ? 1 : 0); + + basic_bigint divisor(256); + + while (n >= 256) + { + basic_bigint q; + basic_bigint r; + n.divide(divisor, q, r, true); + n = q; + data.push_back((uint8_t)(uint64_t)r); + } + if (n >= 0) + { + data.push_back((uint8_t)(uint64_t)n); + } + std::reverse(data.begin(),data.end()); + } + + std::string to_string() const + { + std::string s; + write_string(s); + return s; + } + + template + void write_string(std::basic_string& data) const + { + basic_bigint v(*this); + + size_t len = (v.length() * basic_type_bits / 3) + 2; + data.reserve(len); + + static uint64_t p10 = 1; + static uint64_t ip10 = 0; + + if ( v.length() == 0 ) + { + data.push_back('0'); + } + else + { + uint64_t r; + if ( p10 == 1 ) + { + while ( p10 <= (std::numeric_limits::max)()/10 ) + { + p10 *= 10; + ip10++; + } + } + // p10 is max unsigned power of 10 + basic_bigint R; + basic_bigint LP10 = p10; // LP10 = p10 = ::pow(10, ip10) + + do + { + v.divide( LP10, v, R, true ); + r = (R.length() ? R.data()[0] : 0); + for ( size_type j=0; j < ip10; j++ ) + { + data.push_back(char(r % 10 + '0')); + r /= 10; + if ( r + v.length() == 0 ) + break; + } + } + while ( v.length() ); + if (is_negative()) + { + data.push_back('-'); + } + std::reverse(data.begin(),data.end()); + } + } + + std::string to_string_hex() const + { + std::string s; + write_string_hex(s); + return s; + } + + template + void write_string_hex(std::basic_string& data) const + { + basic_bigint v(*this); + + std::size_t len = (v.length() * basic_bigint::basic_type_bits / 3) + 2; + data.reserve(len); + // 1/3 > ln(2)/ln(10) + static uint64_t p10 = 1; + static uint64_t ip10 = 0; + + if ( v.length() == 0 ) + { + data.push_back('0'); + } + else + { + uint64_t r; + if ( p10 == 1 ) + { + while ( p10 <= (std::numeric_limits::max)()/16 ) + { + p10 *= 16; + ip10++; + } + } // p10 is max unsigned power of 16 + basic_bigint R; + basic_bigint LP10 = p10; // LP10 = p10 = ::pow(16, ip10) + do + { + v.divide( LP10, v, R, true ); + r = (R.length() ? R.data()[0] : 0); + for ( size_type j=0; j < ip10; j++ ) + { + uint8_t c = r % 16; + data.push_back((c < 10) ? ('0' + c) : ('A' - 10 + c)); + r /= 16; + if ( r + v.length() == 0 ) + break; + } + } + while (v.length()); + + if (is_negative()) + { + data.push_back('-'); + } + std::reverse(data.begin(),data.end()); + } + } + +// Global Operators + + friend bool operator==( const basic_bigint& x, const basic_bigint& y ) noexcept + { + return x.compare(y) == 0 ? true : false; + } + + friend bool operator==( const basic_bigint& x, int y ) noexcept + { + return x.compare(y) == 0 ? true : false; + } + + friend bool operator!=( const basic_bigint& x, const basic_bigint& y ) noexcept + { + return x.compare(y) != 0 ? true : false; + } + + friend bool operator!=( const basic_bigint& x, int y ) noexcept + { + return x.compare(basic_bigint(y)) != 0 ? true : false; + } + + friend bool operator<( const basic_bigint& x, const basic_bigint& y ) noexcept + { + return x.compare(y) < 0 ? true : false; + } + + friend bool operator<( const basic_bigint& x, int64_t y ) noexcept + { + return x.compare(y) < 0 ? true : false; + } + + friend bool operator>( const basic_bigint& x, const basic_bigint& y ) noexcept + { + return x.compare(y) > 0 ? true : false; + } + + friend bool operator>( const basic_bigint& x, int y ) noexcept + { + return x.compare(basic_bigint(y)) > 0 ? true : false; + } + + friend bool operator<=( const basic_bigint& x, const basic_bigint& y ) noexcept + { + return x.compare(y) <= 0 ? true : false; + } + + friend bool operator<=( const basic_bigint& x, int y ) noexcept + { + return x.compare(y) <= 0 ? true : false; + } + + friend bool operator>=( const basic_bigint& x, const basic_bigint& y ) noexcept + { + return x.compare(y) >= 0 ? true : false; + } + + friend bool operator>=( const basic_bigint& x, int y ) noexcept + { + return x.compare(y) >= 0 ? true : false; + } + + friend basic_bigint operator+( basic_bigint x, const basic_bigint& y ) + { + return x += y; + } + + friend basic_bigint operator+( basic_bigint x, int64_t y ) + { + return x += y; + } + + friend basic_bigint operator-( basic_bigint x, const basic_bigint& y ) + { + return x -= y; + } + + friend basic_bigint operator-( basic_bigint x, int64_t y ) + { + return x -= y; + } + + friend basic_bigint operator*( int64_t x, const basic_bigint& y ) + { + return basic_bigint(y) *= x; + } + + friend basic_bigint operator*( basic_bigint x, const basic_bigint& y ) + { + return x *= y; + } + + friend basic_bigint operator*( basic_bigint x, int64_t y ) + { + return x *= y; + } + + friend basic_bigint operator/( basic_bigint x, const basic_bigint& y ) + { + return x /= y; + } + + friend basic_bigint operator/( basic_bigint x, int y ) + { + return x /= y; + } + + friend basic_bigint operator%( basic_bigint x, const basic_bigint& y ) + { + return x %= y; + } + + friend basic_bigint operator<<( basic_bigint u, unsigned k ) + { + return u <<= k; + } + + friend basic_bigint operator<<( basic_bigint u, int k ) + { + return u <<= k; + } + + friend basic_bigint operator>>( basic_bigint u, unsigned k ) + { + return u >>= k; + } + + friend basic_bigint operator>>( basic_bigint u, int k ) + { + return u >>= k; + } + + friend basic_bigint operator|( basic_bigint x, const basic_bigint& y ) + { + return x |= y; + } + + friend basic_bigint operator|( basic_bigint x, int y ) + { + return x |= y; + } + + friend basic_bigint operator|( basic_bigint x, unsigned y ) + { + return x |= y; + } + + friend basic_bigint operator^( basic_bigint x, const basic_bigint& y ) + { + return x ^= y; + } + + friend basic_bigint operator^( basic_bigint x, int y ) + { + return x ^= y; + } + + friend basic_bigint operator^( basic_bigint x, unsigned y ) + { + return x ^= y; + } + + friend basic_bigint operator&( basic_bigint x, const basic_bigint& y ) + { + return x &= y; + } + + friend basic_bigint operator&( basic_bigint x, int y ) + { + return x &= y; + } + + friend basic_bigint operator&( basic_bigint x, unsigned y ) + { + return x &= y; + } + + friend basic_bigint abs( const basic_bigint& a ) + { + if ( a.is_negative() ) + { + return -a; + } + return a; + } + + friend basic_bigint power( basic_bigint x, unsigned n ) + { + basic_bigint y = 1; + + while ( n ) + { + if ( n & 1 ) + { + y *= x; + } + x *= x; + n >>= 1; + } + + return y; + } + + friend basic_bigint sqrt( const basic_bigint& a ) + { + basic_bigint x = a; + basic_bigint b = a; + basic_bigint q; + + b <<= 1; + while ( b >>= 2, b > 0 ) + { + x >>= 1; + } + while ( x > (q = a/x) + 1 || x < q - 1 ) + { + x += q; + x >>= 1; + } + return x < q ? x : q; + } + + template + friend std::basic_ostream& operator<<(std::basic_ostream& os, const basic_bigint& v) + { + std::basic_string s; + v.write_string(s); + os << s; + + return os; + } + + int compare( const basic_bigint& y ) const noexcept + { + if ( is_negative() != y.is_negative() ) + return y.is_negative() - is_negative(); + int code = 0; + if ( length() == 0 && y.length() == 0 ) + code = 0; + else if ( length() < y.length() ) + code = -1; + else if ( length() > y.length() ) + code = +1; + else + { + for (size_type i = length(); i-- > 0; ) + { + if (data()[i] > y.data()[i]) + { + code = 1; + break; + } + else if (data()[i] < y.data()[i]) + { + code = -1; + break; + } + } + } + return is_negative() ? -code : code; + } + + void divide( basic_bigint denom, basic_bigint& quot, basic_bigint& rem, bool remDesired ) const + { + if ( denom.length() == 0 ) + { + JSONCONS_THROW(std::runtime_error( "Zero divide." )); + } + bool quot_neg = is_negative() ^ denom.is_negative(); + bool rem_neg = is_negative(); + int x = 0; + basic_bigint num = *this; + num.common_stor_.is_negative_ = denom.common_stor_.is_negative_ = false; + if ( num < denom ) + { + quot = uint64_t(0); + rem = num; + rem.common_stor_.is_negative_ = rem_neg; + return; + } + if ( denom.length() == 1 && num.length() == 1 ) + { + quot = uint64_t( num.data()[0]/denom.data()[0] ); + rem = uint64_t( num.data()[0]%denom.data()[0] ); + quot.common_stor_.is_negative_ = quot_neg; + rem.common_stor_.is_negative_ = rem_neg; + return; + } + else if (denom.length() == 1 && (denom.data()[0] & l_mask) == 0 ) + { + // Denominator fits into a half word + uint64_t divisor = denom.data()[0], dHi = 0, + q1, r, q2, dividend; + quot.resize(length()); + for (size_type i=length(); i-- > 0; ) + { + dividend = (dHi << basic_type_halfBits) | (data()[i] >> basic_type_halfBits); + q1 = dividend/divisor; + r = dividend % divisor; + dividend = (r << basic_type_halfBits) | (data()[i] & r_mask); + q2 = dividend/divisor; + dHi = dividend % divisor; + quot.data()[i] = (q1 << basic_type_halfBits) | q2; + } + quot.reduce(); + rem = dHi; + quot.common_stor_.is_negative_ = quot_neg; + rem.common_stor_.is_negative_ = rem_neg; + return; + } + basic_bigint num0 = num, denom0 = denom; + int second_done = normalize(denom, num, x); + size_type l = denom.length() - 1; + size_type n = num.length() - 1; + quot.resize(n - l); + for (size_type i=quot.length(); i-- > 0; ) + quot.data()[i] = 0; + rem = num; + if ( rem.data()[n] >= denom.data()[l] ) + { + rem.resize(rem.length() + 1); + n++; + quot.resize(quot.length() + 1); + } + uint64_t d = denom.data()[l]; + for ( size_type k = n; k > l; k-- ) + { + uint64_t q = DDquotient(rem.data()[k], rem.data()[k-1], d); + subtractmul( rem.data() + k - l - 1, denom.data(), l + 1, q ); + quot.data()[k - l - 1] = q; + } + quot.reduce(); + quot.common_stor_.is_negative_ = quot_neg; + if ( remDesired ) + { + unnormalize(rem, x, second_done); + rem.common_stor_.is_negative_ = rem_neg; + } + } +private: + void destroy() noexcept + { + if (is_dynamic()) + { + dynamic_stor_.destroy(get_allocator()); + } + } + void DDproduct( uint64_t A, uint64_t B, + uint64_t& hi, uint64_t& lo ) const + // Multiplying two digits: (hi, lo) = A * B + { + uint64_t hiA = A >> basic_type_halfBits, loA = A & r_mask, + hiB = B >> basic_type_halfBits, loB = B & r_mask, + mid1, mid2, old; + + lo = loA * loB; + hi = hiA * hiB; + mid1 = loA * hiB; + mid2 = hiA * loB; + old = lo; + lo += mid1 << basic_type_halfBits; + hi += (lo < old) + (mid1 >> basic_type_halfBits); + old = lo; + lo += mid2 << basic_type_halfBits; + hi += (lo < old) + (mid2 >> basic_type_halfBits); + } + + uint64_t DDquotient( uint64_t A, uint64_t B, uint64_t d ) const + // Divide double word (A, B) by d. Quotient = (qHi, qLo) + { + uint64_t left, middle, right, qHi, qLo, x, dLo1, + dHi = d >> basic_type_halfBits, dLo = d & r_mask; + qHi = A/(dHi + 1); + // This initial guess of qHi may be too small. + middle = qHi * dLo; + left = qHi * dHi; + x = B - (middle << basic_type_halfBits); + A -= (middle >> basic_type_halfBits) + left + (x > B); + B = x; + dLo1 = dLo << basic_type_halfBits; + // Increase qHi if necessary: + while ( A > dHi || (A == dHi && B >= dLo1) ) + { + x = B - dLo1; + A -= dHi + (x > B); + B = x; + qHi++; + } + qLo = ((A << basic_type_halfBits) | (B >> basic_type_halfBits))/(dHi + 1); + // This initial guess of qLo may be too small. + right = qLo * dLo; + middle = qLo * dHi; + x = B - right; + A -= (x > B); + B = x; + x = B - (middle << basic_type_halfBits); + A -= (middle >> basic_type_halfBits) + (x > B); + B = x; + // Increase qLo if necessary: + while ( A || B >= d ) + { + x = B - d; + A -= (x > B); + B = x; + qLo++; + } + return (qHi << basic_type_halfBits) + qLo; + } + + void subtractmul( uint64_t* a, uint64_t* b, size_type n, uint64_t& q ) const + // a -= q * b: b in n positions; correct q if necessary + { + uint64_t hi, lo, d, carry = 0; + size_type i; + for ( i = 0; i < n; i++ ) + { + DDproduct( b[i], q, hi, lo ); + d = a[i]; + a[i] -= lo; + if ( a[i] > d ) + carry++; + d = a[i + 1]; + a[i + 1] -= hi + carry; + carry = a[i + 1] > d; + } + if ( carry ) // q was too large + { + q--; + carry = 0; + for ( i = 0; i < n; i++ ) + { + d = a[i] + carry; + carry = d < carry; + a[i] = d + b[i]; + if ( a[i] < d ) + carry = 1; + } + a[n] = 0; + } + } + + int normalize( basic_bigint& denom, basic_bigint& num, int& x ) const + { + size_type r = denom.length() - 1; + uint64_t y = denom.data()[r]; + + x = 0; + while ( (y & l_bit) == 0 ) + { + y <<= 1; + x++; + } + denom <<= x; + num <<= x; + if ( r > 0 && denom.data()[r] < denom.data()[r-1] ) + { + denom *= max_basic_type; + num *= max_basic_type; + return 1; + } + return 0; + } + + void unnormalize( basic_bigint& rem, int x, int secondDone ) const + { + if ( secondDone ) + { + rem /= max_basic_type; + } + if ( x > 0 ) + { + rem >>= x; + } + else + { + rem.reduce(); + } + } + + size_type round_up(size_type i) const // Find suitable new block size + { + return (i/word_length + 1) * word_length; + } + + void reduce() + { + uint64_t* p = end() - 1; + uint64_t* pBegin = begin(); + while ( p >= pBegin ) + { + if ( *p ) + { + break; + } + --common_stor_.length_; + --p; + } + if ( length() == 0 ) + { + common_stor_.is_negative_ = false; + } + } + + static uint64_t next_power_of_two(uint64_t n) { + n = n - 1; + n |= n >> 1; + n |= n >> 2; + n |= n >> 4; + n |= n >> 8; + n |= n >> 16; + n |= n >> 32; + return n + 1; + } +}; + +using bigint = basic_bigint>; + +} + +#endif diff --git a/third_party/jsoncons/byte_string.hpp b/third_party/jsoncons/byte_string.hpp new file mode 100644 index 0000000000..381333a715 --- /dev/null +++ b/third_party/jsoncons/byte_string.hpp @@ -0,0 +1,805 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BYTE_STRING_HPP +#define JSONCONS_BYTE_STRING_HPP + +#include +#include +#include +#include +#include // std::memcmp +#include // std::allocator +#include +#include +#include // std::setw +#include +#include // std::move +#include +#include +#include +#include + +namespace jsoncons { + + // Algorithms + +namespace detail { + + template + typename std::enable_if::value_type,uint8_t>::value,size_t>::type + encode_base64_generic(InputIt first, InputIt last, const char alphabet[65], Container& result) + { + std::size_t count = 0; + unsigned char a3[3]; + unsigned char a4[4]; + unsigned char fill = alphabet[64]; + int i = 0; + int j = 0; + + while (first != last) + { + a3[i++] = *first++; + if (i == 3) + { + a4[0] = (a3[0] & 0xfc) >> 2; + a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); + a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); + a4[3] = a3[2] & 0x3f; + + for (i = 0; i < 4; i++) + { + result.push_back(alphabet[a4[i]]); + ++count; + } + i = 0; + } + } + + if (i > 0) + { + for (j = i; j < 3; ++j) + { + a3[j] = 0; + } + + a4[0] = (a3[0] & 0xfc) >> 2; + a4[1] = ((a3[0] & 0x03) << 4) + ((a3[1] & 0xf0) >> 4); + a4[2] = ((a3[1] & 0x0f) << 2) + ((a3[2] & 0xc0) >> 6); + + for (j = 0; j < i + 1; ++j) + { + result.push_back(alphabet[a4[j]]); + ++count; + } + + if (fill != 0) + { + while (i++ < 3) + { + result.push_back(fill); + ++count; + } + } + } + + return count; + } + + template + typename std::enable_if::value,decode_result>::type + decode_base64_generic(InputIt first, InputIt last, + const uint8_t reverse_alphabet[256], + F f, + Container& result) + { + uint8_t a4[4], a3[3]; + uint8_t i = 0; + uint8_t j = 0; + + while (first != last && *first != '=') + { + if (!f(*first)) + { + return decode_result{first, conv_errc::conversion_failed}; + } + + a4[i++] = static_cast(*first++); + if (i == 4) + { + for (i = 0; i < 4; ++i) + { + a4[i] = reverse_alphabet[a4[i]]; + } + + a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); + a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); + a3[2] = ((a4[2] & 0x3) << 6) + a4[3]; + + for (i = 0; i < 3; i++) + { + result.push_back(a3[i]); + } + i = 0; + } + } + + if (i > 0) + { + for (j = 0; j < i; ++j) + { + a4[j] = reverse_alphabet[a4[j]]; + } + + a3[0] = (a4[0] << 2) + ((a4[1] & 0x30) >> 4); + a3[1] = ((a4[1] & 0xf) << 4) + ((a4[2] & 0x3c) >> 2); + + for (j = 0; j < i - 1; ++j) + { + result.push_back(a3[j]); + } + } + return decode_result{last, conv_errc::success}; + } + +} // namespace detail + + template + typename std::enable_if::value_type,uint8_t>::value,size_t>::type + encode_base16(InputIt first, InputIt last, Container& result) + { + static constexpr char characters[] = "0123456789ABCDEF"; + + for (auto it = first; it != last; ++it) + { + uint8_t c = *it; + result.push_back(characters[c >> 4]); + result.push_back(characters[c & 0xf]); + } + return (last-first)*2; + } + + template + typename std::enable_if::value_type,uint8_t>::value,size_t>::type + encode_base64url(InputIt first, InputIt last, Container& result) + { + static constexpr char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789-_" + "\0"; + return detail::encode_base64_generic(first, last, alphabet, result); + } + + template + typename std::enable_if::value_type,uint8_t>::value,size_t>::type + encode_base64(InputIt first, InputIt last, Container& result) + { + static constexpr char alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/" + "="; + return detail::encode_base64_generic(first, last, alphabet, result); + } + + template + bool is_base64(Char c) + { + return (c >= 0 && c < 128) && (isalnum((int)c) || c == '+' || c == '/'); + } + + template + bool is_base64url(Char c) + { + return (c >= 0 && c < 128) && (isalnum((int)c) || c == '-' || c == '_'); + } + + inline + static bool is_base64url(int c) + { + return isalnum(c) || c == '-' || c == '_'; + } + + // decode + + template + typename std::enable_if::value,decode_result>::type + decode_base64url(InputIt first, InputIt last, Container& result) + { + static constexpr uint8_t reverse_alphabet[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 62, 0xff, 0xff, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0xff, 0xff, 0xff, 0xff, 63, + 0xff, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + auto retval = jsoncons::detail::decode_base64_generic(first, last, reverse_alphabet, + is_base64url::value_type>, + result); + return retval.ec == conv_errc::success ? retval : decode_result{retval.it, conv_errc::not_base64url}; + } + + template + typename std::enable_if::value,decode_result>::type + decode_base64(InputIt first, InputIt last, Container& result) + { + static constexpr uint8_t reverse_alphabet[256] = { + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 62, 0xff, 0xff, 0xff, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff + }; + auto retval = jsoncons::detail::decode_base64_generic(first, last, reverse_alphabet, + is_base64::value_type>, + result); + return retval.ec == conv_errc::success ? retval : decode_result{retval.it, conv_errc::not_base64}; + } + + template + typename std::enable_if::value,decode_result>::type + decode_base16(InputIt first, InputIt last, Container& result) + { + std::size_t len = std::distance(first,last); + if (len & 1) + { + return decode_result{first, conv_errc::not_base16}; + } + + InputIt it = first; + while (it != last) + { + uint8_t val; + auto a = *it++; + if (a >= '0' && a <= '9') + { + val = static_cast(a - '0') << 4; + } + else if ((a | 0x20) >= 'a' && (a | 0x20) <= 'f') + { + val = (static_cast((a | 0x20) - 'a') + 10) << 4; + } + else + { + return decode_result{first, conv_errc::not_base16}; + } + + auto b = *it++; + if (b >= '0' && b <= '9') + { + val |= (b - '0'); + } + else if ((b | 0x20) >= 'a' && (b | 0x20) <= 'f') + { + val |= ((b | 0x20) - 'a' + 10); + } + else + { + return decode_result{first, conv_errc::not_base16}; + } + + result.push_back(val); + } + return decode_result{last, conv_errc::success}; + } + + struct byte_traits + { + using char_type = uint8_t; + + static constexpr int eof() + { + return std::char_traits::eof(); + } + + static int compare(const char_type* s1, const char_type* s2, std::size_t count) noexcept + { + return std::memcmp(s1,s2,count); + } + }; + + // basic_byte_string + + template + class basic_byte_string; + + // byte_string_view + class byte_string_view + { + const uint8_t* data_; + std::size_t size_; + public: + using traits_type = byte_traits; + + using const_iterator = const uint8_t*; + using iterator = const_iterator; + using size_type = std::size_t; + using value_type = uint8_t; + using reference = uint8_t&; + using const_reference = const uint8_t&; + using difference_type = std::ptrdiff_t; + using pointer = uint8_t*; + using const_pointer = const uint8_t*; + + constexpr byte_string_view() noexcept + : data_(nullptr), size_(0) + { + } + + constexpr byte_string_view(const uint8_t* data, std::size_t length) noexcept + : data_(data), size_(length) + { + } + + template + constexpr explicit byte_string_view(const Container& cont, + typename std::enable_if::value,int>::type = 0) + : data_(reinterpret_cast(cont.data())), size_(cont.size()) + { + } + + template + constexpr byte_string_view(const basic_byte_string& bytes); + + constexpr byte_string_view(const byte_string_view&) noexcept = default; + + JSONCONS_CPP14_CONSTEXPR byte_string_view(byte_string_view&& other) noexcept + : data_(nullptr), size_(0) + { + const_pointer temp_data = data_; + data_ = other.data_; + other.data_ = temp_data; + + size_type temp_size = size_; + size_ = other.size_; + other.size_ = temp_size; + } + + byte_string_view& operator=(const byte_string_view&) = default; + + byte_string_view& operator=(byte_string_view&& other) noexcept + { + std::swap(data_, other.data_); + std::swap(size_, other.size_); + return *this; + } + + constexpr const uint8_t* data() const noexcept + { + return data_; + } + constexpr size_t size() const noexcept + { + return size_; + } + + // iterator support + constexpr const_iterator begin() const noexcept + { + return data_; + } + constexpr const_iterator end() const noexcept + { + return data_ + size_; + } + constexpr const_iterator cbegin() const noexcept + { + return data_; + } + constexpr const_iterator cend() const noexcept + { + return data_ + size_; + } + + constexpr uint8_t operator[](size_type pos) const + { + return data_[pos]; + } + + JSONCONS_CPP14_CONSTEXPR byte_string_view substr(size_type pos) const + { + if (pos > size_) + { + JSONCONS_THROW(std::out_of_range("pos exceeds size")); + } + std::size_t n = size_ - pos; + return byte_string_view(data_ + pos, n); + } + + byte_string_view substr(size_type pos, size_type n) const + { + if (pos > size_) + { + JSONCONS_THROW(std::out_of_range("pos exceeds size")); + } + if (pos + n > size_) + { + n = size_ - pos; + } + return byte_string_view(data_ + pos, n); + } + + int compare(const byte_string_view& s) const noexcept + { + const int rc = traits_type::compare(data_, s.data(), (std::min)(size_, s.size())); + return rc != 0 ? rc : (size_ == s.size() ? 0 : size_ < s.size() ? -1 : 1); + } + + template + int compare(const basic_byte_string& s) const noexcept + { + const int rc = traits_type::compare(data_, s.data(), (std::min)(size_, s.size())); + return rc != 0 ? rc : (size_ == s.size() ? 0 : size_ < s.size() ? -1 : 1); + } + + template + friend std::basic_ostream& operator<<(std::basic_ostream& os, const byte_string_view& bstr) + { + std::basic_ostringstream ss; + ss.flags(std::ios::hex); + ss.fill('0'); + + bool first = true; + for (auto b : bstr) + { + if (first) + { + first = false; + } + else + { + ss << ','; + } + ss << std::setw(2) << static_cast(b); + } + os << ss.str(); + return os; + } + }; + + // basic_byte_string + template > + class basic_byte_string + { + using byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; + std::vector data_; + public: + using traits_type = byte_traits; + using allocator_type = byte_allocator_type; + + using value_type = typename std::vector::value_type; + using size_type = typename std::vector::size_type; + using difference_type = typename std::vector::difference_type; + using reference = typename std::vector::reference; + using const_reference = typename std::vector::const_reference; + using pointer = typename std::vector::pointer; + using const_pointer = typename std::vector::const_pointer; + using iterator = typename std::vector::iterator; + using const_iterator = typename std::vector::const_iterator; + + basic_byte_string() = default; + + explicit basic_byte_string(const Allocator& alloc) + : data_(alloc) + { + } + + basic_byte_string(std::initializer_list init) + : data_(std::move(init)) + { + } + + basic_byte_string(std::initializer_list init, const Allocator& alloc) + : data_(std::move(init), alloc) + { + } + + explicit basic_byte_string(const byte_string_view& v) + : data_(v.begin(),v.end()) + { + } + + basic_byte_string(const basic_byte_string& v) + : data_(v.data_) + { + } + + basic_byte_string(basic_byte_string&& v) noexcept + : data_(std::move(v.data_)) + { + } + + basic_byte_string(const byte_string_view& v, const Allocator& alloc) + : data_(v.begin(),v.end(),alloc) + { + } + + basic_byte_string(const uint8_t* data, std::size_t length, const Allocator& alloc = Allocator()) + : data_(data, data+length,alloc) + { + } + + Allocator get_allocator() const + { + return data_.get_allocator(); + } + + basic_byte_string& operator=(const basic_byte_string& s) = default; + + basic_byte_string& operator=(basic_byte_string&& other) noexcept + { + data_.swap(other.data_); + return *this; + } + + void reserve(std::size_t new_cap) + { + data_.reserve(new_cap); + } + + void push_back(uint8_t b) + { + data_.push_back(b); + } + + void assign(const uint8_t* s, std::size_t count) + { + data_.clear(); + data_.insert(data_.end(), s, s+count); + } + + void append(const uint8_t* s, std::size_t count) + { + data_.insert(data_.end(), s, s+count); + } + + void clear() + { + data_.clear(); + } + + uint8_t operator[](size_type pos) const + { + return data_[pos]; + } + + // iterator support + iterator begin() noexcept + { + return data_.begin(); + } + iterator end() noexcept + { + return data_.end(); + } + + const_iterator begin() const noexcept + { + return data_.begin(); + } + const_iterator end() const noexcept + { + return data_.end(); + } + + uint8_t* data() + { + return data_.data(); + } + + const uint8_t* data() const + { + return data_.data(); + } + + std::size_t size() const + { + return data_.size(); + } + + int compare(const byte_string_view& s) const noexcept + { + const int rc = traits_type::compare(data(), s.data(), (std::min)(size(), s.size())); + return rc != 0 ? rc : (size() == s.size() ? 0 : size() < s.size() ? -1 : 1); + } + + int compare(const basic_byte_string& s) const noexcept + { + const int rc = traits_type::compare(data(), s.data(), (std::min)(size(), s.size())); + return rc != 0 ? rc : (size() == s.size() ? 0 : size() < s.size() ? -1 : 1); + } + + template + friend std::basic_ostream& operator<<(std::basic_ostream& os, const basic_byte_string& o) + { + os << byte_string_view(o); + return os; + } + }; + + template + constexpr byte_string_view::byte_string_view(const basic_byte_string& bytes) + : data_(bytes.data()), size_(bytes.size()) + { + } + + // == + inline + bool operator==(const byte_string_view& lhs, const byte_string_view& rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + template + bool operator==(const byte_string_view& lhs, const basic_byte_string& rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + template + bool operator==(const basic_byte_string& lhs, const byte_string_view& rhs) noexcept + { + return rhs.compare(lhs) == 0; + } + template + bool operator==(const basic_byte_string& lhs, const basic_byte_string& rhs) noexcept + { + return rhs.compare(lhs) == 0; + } + + // != + + inline + bool operator!=(const byte_string_view& lhs, const byte_string_view& rhs) noexcept + { + return lhs.compare(rhs) != 0; + } + template + bool operator!=(const byte_string_view& lhs, const basic_byte_string& rhs) noexcept + { + return lhs.compare(rhs) != 0; + } + template + bool operator!=(const basic_byte_string& lhs, const byte_string_view& rhs) noexcept + { + return rhs.compare(lhs) != 0; + } + template + bool operator!=(const basic_byte_string& lhs, const basic_byte_string& rhs) noexcept + { + return rhs.compare(lhs) != 0; + } + + // <= + + inline + bool operator<=(const byte_string_view& lhs, const byte_string_view& rhs) noexcept + { + return lhs.compare(rhs) <= 0; + } + template + bool operator<=(const byte_string_view& lhs, const basic_byte_string& rhs) noexcept + { + return lhs.compare(rhs) <= 0; + } + template + bool operator<=(const basic_byte_string& lhs, const byte_string_view& rhs) noexcept + { + return rhs.compare(lhs) >= 0; + } + template + bool operator<=(const basic_byte_string& lhs, const basic_byte_string& rhs) noexcept + { + return rhs.compare(lhs) >= 0; + } + + // < + + inline + bool operator<(const byte_string_view& lhs, const byte_string_view& rhs) noexcept + { + return lhs.compare(rhs) < 0; + } + template + bool operator<(const byte_string_view& lhs, const basic_byte_string& rhs) noexcept + { + return lhs.compare(rhs) < 0; + } + template + bool operator<(const basic_byte_string& lhs, const byte_string_view& rhs) noexcept + { + return rhs.compare(lhs) > 0; + } + template + bool operator<(const basic_byte_string& lhs, const basic_byte_string& rhs) noexcept + { + return rhs.compare(lhs) > 0; + } + + // >= + + inline + bool operator>=(const byte_string_view& lhs, const byte_string_view& rhs) noexcept + { + return lhs.compare(rhs) >= 0; + } + template + bool operator>=(const byte_string_view& lhs, const basic_byte_string& rhs) noexcept + { + return lhs.compare(rhs) >= 0; + } + template + bool operator>=(const basic_byte_string& lhs, const byte_string_view& rhs) noexcept + { + return rhs.compare(lhs) <= 0; + } + template + bool operator>=(const basic_byte_string& lhs, const basic_byte_string& rhs) noexcept + { + return rhs.compare(lhs) <= 0; + } + + // > + + inline + bool operator>(const byte_string_view& lhs, const byte_string_view& rhs) noexcept + { + return lhs.compare(rhs) > 0; + } + template + bool operator>(const byte_string_view& lhs, const basic_byte_string& rhs) noexcept + { + return lhs.compare(rhs) > 0; + } + template + bool operator>(const basic_byte_string& lhs, const byte_string_view& rhs) noexcept + { + return rhs.compare(lhs) < 0; + } + template + bool operator>(const basic_byte_string& lhs, const basic_byte_string& rhs) noexcept + { + return rhs.compare(lhs) < 0; + } + + using byte_string = basic_byte_string>; + + namespace extension_traits { + + template + struct is_basic_byte_string + : std::false_type + {}; + + template + struct is_basic_byte_string> + : std::true_type + {}; + + } // namespace extension_traits + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/config/binary_config.hpp b/third_party/jsoncons/config/binary_config.hpp new file mode 100644 index 0000000000..51b52a8d7f --- /dev/null +++ b/third_party/jsoncons/config/binary_config.hpp @@ -0,0 +1,226 @@ +// Copyright 2017 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CONFIG_BINARY_CONFIG_HPP +#define JSONCONS_CONFIG_BINARY_CONFIG_HPP + +#include +#include +#include +#include // std::memcpy +#include +#include // std::enable_if + +// The definitions below follow the definitions in compiler_support_p.h, https://github.com/01org/tinycbor +// MIT license + +#ifdef __F16C__ +# include +#endif + +#ifndef __has_builtin +# define __has_builtin(x) 0 +#endif + +#if defined(__GNUC__) +#if (__GNUC__ * 100 + __GNUC_MINOR__ >= 403) || (__has_builtin(__builtin_bswap64) && __has_builtin(__builtin_bswap32)) +# define JSONCONS_BYTE_SWAP_64 __builtin_bswap64 +# define JSONCONS_BYTE_SWAP_32 __builtin_bswap32 +# ifdef __INTEL_COMPILER +# define JSONCONS_BYTE_SWAP_16 _bswap16 +# elif (__GNUC__ * 100 + __GNUC_MINOR__ >= 608) || __has_builtin(__builtin_bswap16) +# define JSONCONS_BYTE_SWAP_16 __builtin_bswap16 +# endif +#endif +#elif defined(__sun) +# include +#elif defined(_MSC_VER) +// MSVC, which implies sizeof(long) == 4 +# define JSONCONS_BYTE_SWAP_64 _byteswap_uint64 +# define JSONCONS_BYTE_SWAP_32 _byteswap_ulong +# define JSONCONS_BYTE_SWAP_16 _byteswap_ushort +#endif + +namespace jsoncons { +namespace binary { + + struct uint128_holder + { + uint64_t lo; + uint64_t hi; + }; + + static inline bool add_check_overflow(std::size_t v1, std::size_t v2, std::size_t *r) + { + #if ((defined(__GNUC__) && (__GNUC__ >= 5)) && !defined(__INTEL_COMPILER)) || __has_builtin(__builtin_add_overflow) + return __builtin_add_overflow(v1, v2, r); + #else + // unsigned additions are well-defined + *r = v1 + v2; + return v1 > v1 + v2; + #endif + } + + #if defined(__apple_build_version__) && ((__clang_major__ < 8) || ((__clang_major__ == 8) && (__clang_minor__ < 1))) + #define APPLE_MISSING_INTRINSICS 1 + #endif + + inline + uint16_t encode_half(double val) + { + #if defined(__F16C__) && !defined(APPLE_MISSING_INTRINSICS) + return _cvtss_sh((float)val, 3); + #else + uint64_t v; + std::memcpy(&v, &val, sizeof(v)); + int64_t sign = static_cast(v >> 63 << 15); + int64_t exp = (v >> 52) & 0x7ff; + int64_t mant = v << 12 >> 12 >> (53-11); /* keep only the 11 most significant bits of the mantissa */ + exp -= 1023; + if (exp == 1024) { + /* infinity or NaN */ + exp = 16; + mant >>= 1; + } else if (exp >= 16) { + /* overflow, as largest number */ + exp = 15; + mant = 1023; + } else if (exp >= -14) { + /* regular normal */ + } else if (exp >= -24) { + /* subnormal */ + mant |= 1024; + mant >>= -(exp + 14); + exp = -15; + } else { + /* underflow, make zero */ + return 0; + } + + /* safe cast here as bit operations above guarantee not to overflow */ + return static_cast(sign | ((exp + 15) << 10) | mant); + #endif + } + + /* this function was copied & adapted from RFC 7049 Appendix D */ + inline + double decode_half(uint16_t half) + { + #if defined(__F16C__) && !defined(APPLE_MISSING_INTRINSICS) + return _cvtsh_ss(half); + #else + int64_t exp = (half >> 10) & 0x1f; + int64_t mant = half & 0x3ff; + double val; + if (exp == 0) + { + val = ldexp(static_cast(mant), -24); + } + else if (exp != 31) + { + val = ldexp(static_cast(mant) + 1024.0, static_cast(exp - 25)); + } + else + { + val = mant == 0 ? std::numeric_limits::infinity() : std::nan(""); + } + return half & 0x8000 ? -val : val; + #endif + } + + // byte_swap + + template + typename std::enable_if::value && sizeof(T) == sizeof(uint8_t),T>::type + byte_swap(T val) + { + return val; + } + + template + typename std::enable_if::value && sizeof(T) == sizeof(uint16_t),T>::type + byte_swap(T val) + { + #if defined(JSONCONS_BYTE_SWAP_16) + return JSONCONS_BYTE_SWAP_16(val); + #else + return (static_cast(val) >> 8) | (static_cast(val) << 8); + #endif + } + + template + typename std::enable_if::value && sizeof(T) == sizeof(uint32_t),T>::type + byte_swap(T val) + { + #if defined(JSONCONS_BYTE_SWAP_32) + return JSONCONS_BYTE_SWAP_32(val); + #else + uint32_t tmp = ((static_cast(val) << 8) & 0xff00ff00) | ((static_cast(val) >> 8) & 0xff00ff); + return (tmp << 16) | (tmp >> 16); + #endif + } + + template + typename std::enable_if::value && sizeof(T) == sizeof(uint64_t),T>::type + byte_swap(T val) + { + #if defined(JSONCONS_BYTE_SWAP_64) + return JSONCONS_BYTE_SWAP_64(val); + #else + uint64_t tmp = ((static_cast(val) & 0x00000000ffffffffull) << 32) | ((static_cast(val) & 0xffffffff00000000ull) >> 32); + tmp = ((tmp & 0x0000ffff0000ffffull) << 16) | ((tmp & 0xffff0000ffff0000ull) >> 16); + return ((tmp & 0x00ff00ff00ff00ffull) << 8) | ((tmp & 0xff00ff00ff00ff00ull) >> 8); + #endif + } + + template + typename std::enable_if::value && sizeof(T) == sizeof(uint32_t),T>::type + byte_swap(T val) + { + uint32_t x; + std::memcpy(&x,&val,sizeof(uint32_t)); + uint32_t y = byte_swap(x); + T val2; + std::memcpy(&val2,&y,sizeof(uint32_t)); + return val2; + } + + template + typename std::enable_if::value && sizeof(T) == sizeof(uint64_t),T>::type + byte_swap(T val) + { + uint64_t x; + std::memcpy(&x,&val,sizeof(uint64_t)); + uint64_t y = byte_swap(x); + T val2; + std::memcpy(&val2,&y,sizeof(uint64_t)); + return val2; + } + + template + typename std::enable_if::value && sizeof(T) == 2*sizeof(uint64_t),T>::type + byte_swap(T val) + { + uint128_holder x; + uint8_t buf[2*sizeof(uint64_t)]; + std::memcpy(buf,&val,2*sizeof(uint64_t)); + std::memcpy(&x.lo,buf,sizeof(uint64_t)); + std::memcpy(&x.hi,buf+sizeof(uint64_t),sizeof(uint64_t)); + + uint128_holder y; + y.lo = byte_swap(x.hi); + y.hi = byte_swap(x.lo); + + T val2; + std::memcpy(&val2,&y,2*sizeof(uint64_t)); + + return val2; + } + +} // binary +} // jsoncons + +#endif diff --git a/third_party/jsoncons/config/compiler_support.hpp b/third_party/jsoncons/config/compiler_support.hpp new file mode 100644 index 0000000000..50889508a8 --- /dev/null +++ b/third_party/jsoncons/config/compiler_support.hpp @@ -0,0 +1,417 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_COMPILER_SUPPORT_HPP +#define JSONCONS_COMPILER_SUPPORT_HPP + +#include +#include +#include +#include +#include + +#if defined(__GNUC__) +# if defined(__GNUC_PATCHLEVEL__) +# define JSONCONS_GCC_AVAILABLE(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) \ + >= (major * 10000 + minor * 100 + patch)) +# else +# define JSONCONS_GCC_AVAILABLE(major, minor, patch) \ + ((__GNUC__ * 10000 + __GNUC_MINOR__ * 100) \ + >= (major * 10000 + minor * 100 + patch)) +# endif +# else +# define JSONCONS_GCC_AVAILABLE(major, minor, patch) 0 +#endif + +#if defined(__clang__) +# define JSONCONS_CLANG_AVAILABLE(major, minor, patch) \ + ((__clang_major__ * 10000 + __clang_minor__ * 100 + __clang_patchlevel__) \ + >= (major * 10000 + minor * 100 + patch)) +# else +# define JSONCONS_CLANG_AVAILABLE(major, minor, patch) 0 +#endif + +// Uncomment the following line to suppress deprecated names (recommended for new code) +//#define JSONCONS_NO_DEPRECATED + +// The definitions below follow the definitions in compiler_support_p.h, https://github.com/01org/tinycbor +// MIT license + +// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=54577 + +#if defined(__clang__) +# define JSONCONS_FALLTHROUGH [[clang::fallthrough]] +#elif defined(__GNUC__) && ((__GNUC__ >= 7)) +# define JSONCONS_FALLTHROUGH __attribute__((fallthrough)) +#elif defined (__GNUC__) +# define JSONCONS_FALLTHROUGH // FALLTHRU +#else +# define JSONCONS_FALLTHROUGH +#endif + +#if defined(__GNUC__) || defined(__clang__) +#define JSONCONS_LIKELY(x) __builtin_expect(!!(x), 1) +#define JSONCONS_UNLIKELY(x) __builtin_expect(!!(x), 0) +#define JSONCONS_UNREACHABLE() __builtin_unreachable() +#elif defined(_MSC_VER) +#define JSONCONS_LIKELY(x) x +#define JSONCONS_UNLIKELY(x) x +#define JSONCONS_UNREACHABLE() __assume(0) +#else +#define JSONCONS_LIKELY(x) x +#define JSONCONS_UNLIKELY(x) x +#define JSONCONS_UNREACHABLE() do {} while (0) +#endif + +// Deprecated symbols markup +#if (defined(__cplusplus) && __cplusplus >= 201402L) +#define JSONCONS_DEPRECATED_MSG(msg) [[deprecated(msg)]] +#endif + +#if !defined(JSONCONS_DEPRECATED_MSG) && defined(__GNUC__) && defined(__has_extension) +#if __has_extension(attribute_deprecated_with_message) +#define JSONCONS_DEPRECATED_MSG(msg) __attribute__((deprecated(msg))) +#endif +#endif + +#if !defined(JSONCONS_DEPRECATED_MSG) && defined(_MSC_VER) +#if (_MSC_VER) >= 1920 +#define JSONCONS_DEPRECATED_MSG(msg) [[deprecated(msg)]] +#else +#define JSONCONS_DEPRECATED_MSG(msg) __declspec(deprecated(msg)) +#endif +#endif + +// Following boost/atomic/detail/config.hpp +#if !defined(JSONCONS_DEPRECATED_MSG) && (\ + (defined(__GNUC__) && ((__GNUC__ + 0) * 100 + (__GNUC_MINOR__ + 0)) >= 405) ||\ + (defined(__SUNPRO_CC) && (__SUNPRO_CC + 0) >= 0x5130)) + #define JSONCONS_DEPRECATED_MSG(msg) __attribute__((deprecated(msg))) +#endif + +#if !defined(JSONCONS_DEPRECATED_MSG) && defined(__clang__) && defined(__has_extension) + #if __has_extension(attribute_deprecated_with_message) + #define JSONCONS_DEPRECATED_MSG(msg) __attribute__((deprecated(msg))) + #else + #define JSONCONS_DEPRECATED_MSG(msg) __attribute__((deprecated)) + #endif +#endif + +#if !defined(JSONCONS_DEPRECATED_MSG) +#define JSONCONS_DEPRECATED_MSG(msg) +#endif + +#if defined(ANDROID) || defined(__ANDROID__) +#if __ANDROID_API__ >= 21 +#define JSONCONS_HAS_STRTOLD_L +#else +#define JSONCONS_NO_LOCALECONV +#endif +#endif + +#if defined(_MSC_VER) +#define JSONCONS_HAS_MSC_STRTOD_L +#define JSONCONS_HAS_FOPEN_S +#endif + +#ifndef JSONCONS_HAS_CP14 + #if defined(_MSVC_LANG) + #if _MSVC_LANG >= 201402L + #define JSONCONS_HAS_CP14 + #endif + #elif __cplusplus >= 201402L + #define JSONCONS_HAS_CP14 + #endif +#endif + +#if defined(JSONCONS_HAS_STD_FROM_CHARS) && JSONCONS_HAS_STD_FROM_CHARS +#include +#endif + +#if !defined(JSONCONS_HAS_2017) +# if defined(__clang__) +# if (__cplusplus >= 201703) +# define JSONCONS_HAS_2017 1 +# endif // (__cplusplus >= 201703) +# endif // defined(__clang__) +# if defined(__GNUC__) +# if (__GNUC__ >= 7) +# if (__cplusplus >= 201703) +# define JSONCONS_HAS_2017 1 +# endif // (__cplusplus >= 201703) +# endif // (__GNUC__ >= 7) +# endif // defined(__GNUC__) +# if defined(_MSC_VER) +# if (_MSC_VER >= 1910 && _MSVC_LANG >= 201703) +# define JSONCONS_HAS_2017 1 +# endif // (_MSC_VER >= 1910 && MSVC_LANG >= 201703) +# endif // defined(_MSC_VER) +#endif + +#if defined(JSONCONS_HAS_2017) + #define JSONCONS_NODISCARD [[nodiscard]] + #define JSONCONS_IF_CONSTEXPR if constexpr +#else + #define JSONCONS_NODISCARD + #define JSONCONS_IF_CONSTEXPR if +#endif + +#if !defined(JSONCONS_HAS_POLYMORPHIC_ALLOCATOR) +#if defined(JSONCONS_HAS_2017) +# if __has_include() +# define JSONCONS_HAS_POLYMORPHIC_ALLOCATOR 1 +# endif // __has_include() +#endif +#endif + +#if !defined(JSONCONS_HAS_STD_STRING_VIEW) +# if (defined JSONCONS_HAS_2017) +# if defined(__clang__) +# if __has_include() +# define JSONCONS_HAS_STD_STRING_VIEW 1 +# endif // __has_include() +# else +# define JSONCONS_HAS_STD_STRING_VIEW 1 +# endif +# endif // defined(JSONCONS_HAS_2017) +#endif // !defined(JSONCONS_HAS_STD_STRING_VIEW) + +#if !defined(JSONCONS_HAS_STD_BYTE) +# if (defined JSONCONS_HAS_2017) +# define JSONCONS_HAS_STD_BYTE 1 +# endif // defined(JSONCONS_HAS_2017) +#endif // !defined(JSONCONS_HAS_STD_BYTE) + +#if !defined(JSONCONS_HAS_STD_OPTIONAL) +# if (defined JSONCONS_HAS_2017) +# if defined(__clang__) +# if __has_include() +# define JSONCONS_HAS_STD_OPTIONAL 1 +# endif // __has_include() +# else +# define JSONCONS_HAS_STD_OPTIONAL 1 +# endif +# endif // defined(JSONCONS_HAS_2017) +#endif // !defined(JSONCONS_HAS_STD_OPTIONAL) + +#if !defined(JSONCONS_HAS_STD_VARIANT) +# if (defined JSONCONS_HAS_2017) +# if defined(__clang__) +# if defined(__APPLE__) +# if JSONCONS_CLANG_AVAILABLE(10,0,1) +# define JSONCONS_HAS_STD_VARIANT 1 +# endif +# elif __has_include() +# define JSONCONS_HAS_STD_VARIANT 1 +# endif // __has_include() +# else +# define JSONCONS_HAS_STD_VARIANT 1 +# endif +# endif // defined(JSONCONS_HAS_2017) +#endif // !defined(JSONCONS_HAS_STD_VARIANT) + +#if !defined(JSONCONS_HAS_FILESYSTEM) +# if (defined JSONCONS_HAS_2017) +# if defined(__clang__) +# if __has_include() +# define JSONCONS_HAS_FILESYSTEM 1 +# endif // __has_include() +# else +# define JSONCONS_HAS_FILESYSTEM 1 +# endif +# endif // defined(JSONCONS_HAS_2017) +#endif // !defined(JSONCONS_HAS_FILESYSTEM) + +#if (!defined(JSONCONS_NO_EXCEPTIONS)) +// Check if exceptions are disabled. +# if defined( __cpp_exceptions) && __cpp_exceptions == 0 +# define JSONCONS_NO_EXCEPTIONS 1 +# endif +#endif + +#if !defined(JSONCONS_NO_EXCEPTIONS) + +#if defined(__GNUC__) && !defined(__EXCEPTIONS) +# define JSONCONS_NO_EXCEPTIONS 1 +#elif defined(_MSC_VER) +#if defined(_HAS_EXCEPTIONS) && _HAS_EXCEPTIONS == 0 +# define JSONCONS_NO_EXCEPTIONS 1 +#elif !defined(_CPPUNWIND) +# define JSONCONS_NO_EXCEPTIONS 1 +#endif +#endif +#endif + +// allow to disable exceptions +#if !defined(JSONCONS_NO_EXCEPTIONS) + #define JSONCONS_THROW(exception) throw exception + #define JSONCONS_RETHROW throw + #define JSONCONS_TRY try + #define JSONCONS_CATCH(exception) catch(exception) +#else + #define JSONCONS_THROW(exception) std::terminate() + #define JSONCONS_RETHROW std::terminate() + #define JSONCONS_TRY if (true) + #define JSONCONS_CATCH(exception) if (false) +#endif + +#if !defined(JSONCONS_HAS_STD_MAKE_UNIQUE) + #if defined(__clang__) && defined(__cplusplus) + #if defined(__APPLE__) + #if __clang_major__ >= 6 && __cplusplus >= 201402L // Xcode 6 + #define JSONCONS_HAS_STD_MAKE_UNIQUE + #endif + #elif ((__clang_major__*100 +__clang_minor__) >= 340) && __cplusplus >= 201402L + #define JSONCONS_HAS_STD_MAKE_UNIQUE + #endif + #elif defined(__GNUC__) + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 409 && __cplusplus > 201103L + #define JSONCONS_HAS_STD_MAKE_UNIQUE + #endif + #elif defined(_MSC_VER) + #if _MSC_VER >= 1800 + #define JSONCONS_HAS_STD_MAKE_UNIQUE + #endif + #endif +#endif // !defined(JSONCONS_HAS_STD_MAKE_UNIQUE) + +#ifndef JSONCONS_HAS_CP14_CONSTEXPR + #if defined(_MSC_VER) + #if _MSC_VER >= 1910 + #define JSONCONS_HAS_CP14_CONSTEXPR + #endif + #elif defined(__GNUC__) + #if (__GNUC__ * 100 + __GNUC_MINOR__) >= 600 && __cplusplus >= 201402L + #define JSONCONS_HAS_CP14_CONSTEXPR + #endif + #endif +#endif + +#if defined(JSONCONS_HAS_CP14_CONSTEXPR) +# define JSONCONS_CPP14_CONSTEXPR constexpr +#else +# define JSONCONS_CPP14_CONSTEXPR +#endif + +// Follows boost + +// gcc and clang +#if !defined(__CUDA_ARCH__) +#if (defined(__clang__) || defined(__GNUC__)) && defined(__cplusplus) +#if defined(__SIZEOF_INT128__) && !defined(_MSC_VER) +# define JSONCONS_HAS_INT128 +#endif + +#if (defined(linux) || defined(__linux) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__)) && !defined(_CRAYC) +#if (__clang_major__ >= 4) && defined(__has_include) +#if __has_include() +# define JSONCONS_HAS_FLOAT128 +#endif +#endif +#endif +#endif + +#if defined(__GNUC__) +#if defined(_GLIBCXX_USE_FLOAT128) +# define JSONCONS_HAS_FLOAT128 +#endif +#endif + +#if defined(__clang__) +#if (defined(linux) || defined(__linux) || defined(__linux__) || defined(__GNU__) || defined(__GLIBC__)) && !defined(_CRAYC) +#if (__clang_major__ >= 4) && defined(__has_include) +#if __has_include() +# define JSONCONS_HAS_FLOAT128 +#endif +#endif +#endif +#endif +#endif // __CUDA_ARCH__ + +// Follows boost config/detail/suffix.hpp +#if defined(JSONCONS_HAS_INT128) && defined(__cplusplus) +namespace jsoncons{ +# ifdef __GNUC__ + __extension__ typedef __int128 int128_type; + __extension__ typedef unsigned __int128 uint128_type; +# else + typedef __int128 int128_type; + typedef unsigned __int128 uint128_type; +# endif +} +#endif +#if defined(JSONCONS_HAS_FLOAT128) && defined(__cplusplus) +namespace jsoncons { +# ifdef __GNUC__ + __extension__ typedef __float128 float128_type; +# else + typedef __float128 float128_type; +# endif +} +#endif + +#if defined(_MSC_VER) && _MSC_VER <= 1900 + #define JSONCONS_COPY(first,last,d_first) std::copy(first, last, stdext::make_checked_array_iterator(d_first, static_cast(std::distance(first, last)))) +#else + #define JSONCONS_COPY(first,last,d_first) std::copy(first, last, d_first) +#endif + +#if defined(JSONCONS_HAS_CP14) +#define JSONCONS_CONSTEXPR constexpr +#else +#define JSONCONS_CONSTEXPR +#endif + +#if !defined(JSONCONS_HAS_STD_REGEX) +#if defined(__clang__) +#define JSONCONS_HAS_STD_REGEX 1 +#elif (defined(__GNUC__) && (__GNUC__ == 4)) && (defined(__GNUC__) && __GNUC_MINOR__ < 9) +// GCC 4.8 has broken regex support: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=53631 +#else +#define JSONCONS_HAS_STD_REGEX 1 +#endif +#endif + +#if !defined(JSONCONS_HAS_STATEFUL_ALLOCATOR) +#if defined(__clang__) && !JSONCONS_CLANG_AVAILABLE(11,0,0) +#elif defined(__GNUC__) && !JSONCONS_GCC_AVAILABLE(10,0,0) +#else +#define JSONCONS_HAS_STATEFUL_ALLOCATOR 1 +#endif +#endif + +namespace jsoncons { + + class assertion_error : public std::runtime_error + { + public: + assertion_error(const std::string& s) noexcept + : std::runtime_error(s) + { + } + const char* what() const noexcept override + { + return std::runtime_error::what(); + } + }; + +} // namespace jsoncons + +#define JSONCONS_STR2(x) #x +#define JSONCONS_STR(x) JSONCONS_STR2(x) + +#ifdef _DEBUG +#define JSONCONS_ASSERT(x) if (!(x)) { \ + JSONCONS_THROW(jsoncons::assertion_error("assertion '" #x "' failed at " __FILE__ ":" \ + JSONCONS_STR(__LINE__))); } +#else +#define JSONCONS_ASSERT(x) if (!(x)) { \ + JSONCONS_THROW(jsoncons::assertion_error("assertion '" #x "' failed at <> :" \ + JSONCONS_STR( 0 ))); } +#endif // _DEBUG + +#endif // JSONCONS_COMPILER_SUPPORT_HPP diff --git a/third_party/jsoncons/config/jsoncons_config.hpp b/third_party/jsoncons/config/jsoncons_config.hpp new file mode 100644 index 0000000000..819cc05d30 --- /dev/null +++ b/third_party/jsoncons/config/jsoncons_config.hpp @@ -0,0 +1,302 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CONFIG_JSONCONS_CONFIG_HPP +#define JSONCONS_CONFIG_JSONCONS_CONFIG_HPP + +#include +#include +#include +#include + +#if !defined(JSONCONS_HAS_STD_STRING_VIEW) +#include +namespace jsoncons { +using jsoncons::detail::basic_string_view; +using string_view = jsoncons::detail::string_view; +using wstring_view = jsoncons::detail::wstring_view; +} +#else +#include +namespace jsoncons { +using std::basic_string_view; +using std::string_view; +using std::wstring_view; +} +#endif + +#if !defined(JSONCONS_HAS_STD_SPAN) +#include +namespace jsoncons { +using jsoncons::detail::span; +} +#else +#include +namespace jsoncons { +using std::span; +} +#endif + +#if defined(JSONCONS_HAS_STD_OPTIONAL) + #include + namespace jsoncons { + using std::optional; + } +#elif defined(JSONCONS_HAS_BOOST_OPTIONAL) + #include + namespace jsoncons { + using boost::optional; + } +#else + #include + namespace jsoncons { + using jsoncons::detail::optional; +} +#endif // !defined(JSONCONS_HAS_STD_OPTIONAL) + +#if !defined(JSONCONS_HAS_STD_ENDIAN) +#include +namespace jsoncons { +using jsoncons::detail::endian; +} +#else +#include +namespace jsoncons +{ + using std::endian; +} +#endif + +#if !defined(JSONCONS_HAS_STD_MAKE_UNIQUE) + +#include +#include +#include +#include + +namespace jsoncons { + + template + struct unique_if + { + using value_is_not_array = std::unique_ptr; + }; + + template + struct unique_if + { + typedef std::unique_ptr value_is_array_of_unknown_bound; + }; + + template + struct unique_if { + using value_is_array_of_known_bound = void; + }; + + template + typename unique_if::value_is_not_array + make_unique(Args&&... args) + { + return std::unique_ptr(new T(std::forward(args)...)); + } + + template + typename unique_if::value_is_array_of_unknown_bound + make_unique(std::size_t n) + { + using U = typename std::remove_extent::type; + return std::unique_ptr(new U[n]()); + } + + template + typename unique_if::value_is_array_of_known_bound + make_unique(Args&&...) = delete; +} // jsoncons + +#else + +#include +namespace jsoncons +{ + using std::make_unique; +} + +#endif // !defined(JSONCONS_HAS_STD_MAKE_UNIQUE) + +namespace jsoncons { +namespace binary { + + // native_to_big + + template + typename std::enable_if::type + native_to_big(T val, OutputIt d_first) + { + uint8_t buf[sizeof(T)]; + std::memcpy(buf, &val, sizeof(T)); + for (auto item : buf) + { + *d_first++ = item; + } + } + + template + typename std::enable_if::type + native_to_big(T val, OutputIt d_first) + { + T val2 = byte_swap(val); + uint8_t buf[sizeof(T)]; + std::memcpy(buf, &val2, sizeof(T)); + for (auto item : buf) + { + *d_first++ = item; + } + } + + // native_to_little + + template + typename std::enable_if::type + native_to_little(T val, OutputIt d_first) + { + uint8_t buf[sizeof(T)]; + std::memcpy(buf, &val, sizeof(T)); + for (auto item : buf) + { + *d_first++ = item; + } + } + + template + typename std::enable_if::type + native_to_little(T val, OutputIt d_first) + { + T val2 = byte_swap(val); + uint8_t buf[sizeof(T)]; + std::memcpy(buf, &val2, sizeof(T)); + for (auto item : buf) + { + *d_first++ = item; + } + } + + // big_to_native + + template + typename std::enable_if::type + big_to_native(const uint8_t* first, std::size_t count) + { + if (sizeof(T) > count) + { + return T{}; + } + T val; + std::memcpy(&val,first,sizeof(T)); + return val; + } + + template + typename std::enable_if::type + big_to_native(const uint8_t* first, std::size_t count) + { + if (sizeof(T) > count) + { + return T{}; + } + T val; + std::memcpy(&val,first,sizeof(T)); + return byte_swap(val); + } + + // little_to_native + + template + typename std::enable_if::type + little_to_native(const uint8_t* first, std::size_t count) + { + if (sizeof(T) > count) + { + return T{}; + } + T val; + std::memcpy(&val,first,sizeof(T)); + return val; + } + + template + typename std::enable_if::type + little_to_native(const uint8_t* first, std::size_t count) + { + if (sizeof(T) > count) + { + return T{}; + } + T val; + std::memcpy(&val,first,sizeof(T)); + return byte_swap(val); + } + +} // binary +} // jsoncons + +namespace jsoncons { + + template + constexpr const CharT* cstring_constant_of_type(const char* c, const wchar_t* w); + + template<> inline + constexpr const char* cstring_constant_of_type(const char* c, const wchar_t*) + { + return c; + } + template<> inline + constexpr const wchar_t* cstring_constant_of_type(const char*, const wchar_t* w) + { + return w; + } + + template + std::basic_string string_constant_of_type(const char* c, const wchar_t* w); + + template<> inline + std::string string_constant_of_type(const char* c, const wchar_t*) + { + return std::string(c); + } + template<> inline + std::wstring string_constant_of_type(const char*, const wchar_t* w) + { + return std::wstring(w); + } + + template + jsoncons::basic_string_view string_view_constant_of_type(const char* c, const wchar_t* w); + + template<> inline + jsoncons::string_view string_view_constant_of_type(const char* c, const wchar_t*) + { + return jsoncons::string_view(c); + } + template<> inline + jsoncons::wstring_view string_view_constant_of_type(const char*, const wchar_t* w) + { + return jsoncons::wstring_view(w); + } + +} // jsoncons + +#define JSONCONS_EXPAND(X) X +#define JSONCONS_QUOTE(Prefix, A) JSONCONS_EXPAND(Prefix ## #A) +#define JSONCONS_WIDEN(A) JSONCONS_EXPAND(L ## A) + +#define JSONCONS_CSTRING_CONSTANT(CharT, Str) cstring_constant_of_type(Str, JSONCONS_WIDEN(Str)) +#define JSONCONS_STRING_CONSTANT(CharT, Str) string_constant_of_type(Str, JSONCONS_WIDEN(Str)) +#define JSONCONS_STRING_VIEW_CONSTANT(CharT, Str) string_view_constant_of_type(Str, JSONCONS_WIDEN(Str)) + + +#endif // JSONCONS_CONFIG_JSONCONS_CONFIG_HPP + + diff --git a/third_party/jsoncons/config/version.hpp b/third_party/jsoncons/config/version.hpp new file mode 100644 index 0000000000..5ec8c5edab --- /dev/null +++ b/third_party/jsoncons/config/version.hpp @@ -0,0 +1,40 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_VERSION_HPP +#define JSONCONS_VERSION_HPP + +#include + +#define JSONCONS_VERSION_MAJOR 0 +#define JSONCONS_VERSION_MINOR 177 +#define JSONCONS_VERSION_PATCH 0 + +namespace jsoncons { + +struct versioning_info +{ + unsigned int const major; + unsigned int const minor; + unsigned int const patch; + + friend std::ostream& operator<<(std::ostream& os, const versioning_info& ver) + { + os << ver.major << '.' + << ver.minor << '.' + << ver.patch; + return os; + } +}; + +constexpr versioning_info version() +{ + return versioning_info{JSONCONS_VERSION_MAJOR, JSONCONS_VERSION_MINOR, JSONCONS_VERSION_PATCH}; +} + +} + +#endif diff --git a/third_party/jsoncons/conv_error.hpp b/third_party/jsoncons/conv_error.hpp new file mode 100644 index 0000000000..854b2fa450 --- /dev/null +++ b/third_party/jsoncons/conv_error.hpp @@ -0,0 +1,214 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CONV_ERROR_HPP +#define JSONCONS_CONV_ERROR_HPP + +#include +#include + +namespace jsoncons { + + class conv_error : public std::system_error, public virtual json_exception + { + std::size_t line_number_; + std::size_t column_number_; + mutable std::string what_; + public: + conv_error(std::error_code ec) + : std::system_error(ec), line_number_(0), column_number_(0) + { + } + conv_error(std::error_code ec, const std::string& what_arg) + : std::system_error(ec, what_arg), line_number_(0), column_number_(0) + { + } + conv_error(std::error_code ec, std::size_t position) + : std::system_error(ec), line_number_(0), column_number_(position) + { + } + conv_error(std::error_code ec, std::size_t line, std::size_t column) + : std::system_error(ec), line_number_(line), column_number_(column) + { + } + conv_error(const conv_error& other) = default; + + conv_error(conv_error&& other) = default; + + const char* what() const noexcept override + { + if (what_.empty()) + { + JSONCONS_TRY + { + what_.append(std::system_error::what()); + if (line_number_ != 0 && column_number_ != 0) + { + what_.append(" at line "); + what_.append(std::to_string(line_number_)); + what_.append(" and column "); + what_.append(std::to_string(column_number_)); + } + else if (column_number_ != 0) + { + what_.append(" at position "); + what_.append(std::to_string(column_number_)); + } + return what_.c_str(); + } + JSONCONS_CATCH(...) + { + return std::system_error::what(); + } + } + else + { + return what_.c_str(); + } + } + + std::size_t line() const noexcept + { + return line_number_; + } + + std::size_t column() const noexcept + { + return column_number_; + } + }; + + enum class conv_errc + { + success = 0, + conversion_failed, + not_utf8, + not_wide_char, + not_vector, + not_array, + not_map, + not_pair, + not_string, + not_string_view, + not_byte_string, + not_byte_string_view, + not_integer, + not_signed_integer, + not_unsigned_integer, + not_bigint, + not_double, + not_bool, + not_variant, + not_nullptr, + not_jsoncons_null_type, + not_bitset, + not_base64, + not_base64url, + not_base16 + }; + + template + struct decode_result + { + InputIt it; + conv_errc ec; + }; + +} // namespace jsoncons + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +namespace jsoncons { + +namespace detail { + class conv_error_category_impl + : public std::error_category + { + public: + const char* name() const noexcept override + { + return "jsoncons/convert"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case conv_errc::conversion_failed: + return "Unable to convert into the provided type"; + case conv_errc::not_utf8: + return "Cannot convert string to UTF-8"; + case conv_errc::not_wide_char: + return "Cannot convert string to wide characters"; + case conv_errc::not_vector: + return "Cannot convert to vector"; + case conv_errc::not_array: + return "Cannot convert to std::array"; + case conv_errc::not_map: + return "Cannot convert to map"; + case conv_errc::not_pair: + return "Cannot convert to std::pair"; + case conv_errc::not_string: + return "Cannot convert to string"; + case conv_errc::not_string_view: + return "Cannot convert to string_view"; + case conv_errc::not_byte_string: + return "Cannot convert to byte_string"; + case conv_errc::not_byte_string_view: + return "Cannot convert to byte_string_view"; + case conv_errc::not_integer: + return "Cannot convert to integer"; + case conv_errc::not_signed_integer: + return "Cannot convert to signed integer"; + case conv_errc::not_unsigned_integer: + return "Cannot convert to unsigned integer"; + case conv_errc::not_bigint: + return "Cannot convert to bigint"; + case conv_errc::not_double: + return "Cannot convert to double"; + case conv_errc::not_bool: + return "Cannot convert to bool"; + case conv_errc::not_variant: + return "Cannot convert to std::variant"; + case conv_errc::not_nullptr: + return "Cannot convert to std::nullptr_t"; + case conv_errc::not_jsoncons_null_type: + return "Cannot convert to jsoncons::null_type"; + case conv_errc::not_bitset: + return "Cannot convert to std::bitset"; + case conv_errc::not_base64: + return "Input is not a base64 encoded string"; + case conv_errc::not_base64url: + return "Input is not a base64url encoded string"; + case conv_errc::not_base16: + return "Input is not a base16 encoded string"; + default: + return "Unknown conversion error"; + } + } + }; +} // detail + +extern inline +const std::error_category& conv_error_category() +{ + static detail::conv_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(conv_errc result) +{ + return std::error_code(static_cast(result),conv_error_category()); +} + +} + +#endif diff --git a/third_party/jsoncons/decode_json.hpp b/third_party/jsoncons/decode_json.hpp new file mode 100644 index 0000000000..7640e4043d --- /dev/null +++ b/third_party/jsoncons/decode_json.hpp @@ -0,0 +1,211 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DECODE_JSON_HPP +#define JSONCONS_DECODE_JSON_HPP + +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include + +namespace jsoncons { + + // decode_json + + template + typename std::enable_if::value && + extension_traits::is_sequence_of::value,T>::type + decode_json(const Source& s, + const basic_json_decode_options& options = basic_json_decode_options()) + { + using char_type = typename Source::value_type; + + jsoncons::json_decoder decoder; + basic_json_reader> reader(s, decoder, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_char_sequence::value,T>::type + decode_json(const Source& s, + const basic_json_decode_options& options = basic_json_decode_options()) + { + using char_type = typename Source::value_type; + + basic_json_cursor> cursor(s, options, default_json_parsing()); + jsoncons::json_decoder> decoder; + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_json(std::basic_istream& is, + const basic_json_decode_options& options = basic_json_decode_options()) + { + jsoncons::json_decoder decoder; + basic_json_reader> reader(is, decoder, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_json(std::basic_istream& is, + const basic_json_decode_options& options = basic_json_decode_options()) + { + basic_json_cursor cursor(is, options, default_json_parsing()); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.line(), cursor.column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_json(InputIt first, InputIt last, + const basic_json_decode_options::value_type>& options = + basic_json_decode_options::value_type>()) + { + using char_type = typename std::iterator_traits::value_type; + + jsoncons::json_decoder decoder; + basic_json_reader> reader(iterator_source(first,last), decoder, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_json(InputIt first, InputIt last, + const basic_json_decode_options::value_type>& options = + basic_json_decode_options::value_type>()) + { + using char_type = typename std::iterator_traits::value_type; + + basic_json_cursor> cursor(iterator_source(first, last), options, default_json_parsing()); + jsoncons::json_decoder> decoder; + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.line(), cursor.column())); + } + return val; + } + + // With leading allocator_set parameter + + template + typename std::enable_if::value && + extension_traits::is_sequence_of::value,T>::type + decode_json(const allocator_set& alloc_set, + const Source& s, + const basic_json_decode_options& options = basic_json_decode_options()) + { + using char_type = typename Source::value_type; + + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + + basic_json_reader,TempAllocator> reader(s, decoder, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_char_sequence::value,T>::type + decode_json(const allocator_set& alloc_set, + const Source& s, + const basic_json_decode_options& options = basic_json_decode_options()) + { + using char_type = typename Source::value_type; + + basic_json_cursor,TempAllocator> cursor(s, options, default_json_parsing(), alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_json(const allocator_set& alloc_set, + std::basic_istream& is, + const basic_json_decode_options& options = basic_json_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + + basic_json_reader,TempAllocator> reader(is, decoder, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_json(const allocator_set& alloc_set, + std::basic_istream& is, + const basic_json_decode_options& options = basic_json_decode_options()) + { + basic_json_cursor,TempAllocator> cursor(is, options, default_json_parsing(), alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(),alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + +} // jsoncons + +#endif + diff --git a/third_party/jsoncons/decode_traits.hpp b/third_party/jsoncons/decode_traits.hpp new file mode 100644 index 0000000000..e4ca9b182d --- /dev/null +++ b/third_party/jsoncons/decode_traits.hpp @@ -0,0 +1,651 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DECODE_TRAITS_HPP +#define JSONCONS_DECODE_TRAITS_HPP + +#include +#include +#include +#include +#include // std::enable_if, std::true_type, std::false_type +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + + // decode_traits + + template + struct decode_traits + { + template + static T decode(basic_staj_cursor& cursor, + json_decoder& decoder, + std::error_code& ec) + { + decoder.reset(); + cursor.read_to(decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + else if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, cursor.context().line(), cursor.context().column())); + } + return decoder.get_result().template as(); + } + }; + + // specializations + + // primitive + + template + struct decode_traits::value + >::type> + { + template + static T decode(basic_staj_cursor& cursor, + json_decoder&, + std::error_code& ec) + { + T v = cursor.current().template get(ec); + return v; + } + }; + + // string + + template + struct decode_traits::value && + std::is_same::value + >::type> + { + template + static T decode(basic_staj_cursor& cursor, + json_decoder&, + std::error_code& ec) + { + T v = cursor.current().template get(ec); + return v; + } + }; + + template + struct decode_traits::value && + !std::is_same::value + >::type> + { + template + static T decode(basic_staj_cursor& cursor, + json_decoder&, + std::error_code& ec) + { + auto val = cursor.current().template get>(ec); + T s; + if (!ec) + { + unicode_traits::convert(val.data(), val.size(), s); + } + return s; + } + }; + + // std::pair + + template + struct decode_traits, CharT> + { + template + static std::pair decode(basic_staj_cursor& cursor, + json_decoder& decoder, + std::error_code& ec) + { + using value_type = std::pair; + cursor.array_expected(ec); + if (ec) + { + return value_type{}; + } + if (cursor.current().event_type() != staj_event_type::begin_array) + { + ec = conv_errc::not_pair; + return value_type(); + } + cursor.next(ec); // skip past array + if (ec) + { + return value_type(); + } + + T1 v1 = decode_traits::decode(cursor, decoder, ec); + if (ec) {return value_type();} + cursor.next(ec); + if (ec) {return value_type();} + T2 v2 = decode_traits::decode(cursor, decoder, ec); + if (ec) {return value_type();} + cursor.next(ec); + + if (cursor.current().event_type() != staj_event_type::end_array) + { + ec = conv_errc::not_pair; + return value_type(); + } + return std::make_pair(v1, v2); + } + }; + + // vector like + template + struct decode_traits::value && + extension_traits::is_array_like::value && + extension_traits::is_back_insertable::value && + !extension_traits::is_typed_array::value + >::type> + { + using value_type = typename T::value_type; + + template + static T decode(basic_staj_cursor& cursor, + json_decoder& decoder, + std::error_code& ec) + { + T v; + + cursor.array_expected(ec); + if (ec) + { + return T{}; + } + if (cursor.current().event_type() != staj_event_type::begin_array) + { + ec = conv_errc::not_vector; + return v; + } + cursor.next(ec); + while (cursor.current().event_type() != staj_event_type::end_array && !ec) + { + v.push_back(decode_traits::decode(cursor, decoder, ec)); + if (ec) {return T{};} + cursor.next(ec); + } + return v; + } + }; + + template + struct typed_array_visitor : public default_json_visitor + { + T& v_; + int level_; + public: + using value_type = typename T::value_type; + + typed_array_visitor(T& v) + : default_json_visitor(false,conv_errc::not_vector), v_(v), level_(0) + { + } + private: + bool visit_begin_array(semantic_tag, + const ser_context&, + std::error_code& ec) override + { + if (++level_ != 1) + { + ec = conv_errc::not_vector; + return false; + } + return true; + } + + bool visit_begin_array(std::size_t size, + semantic_tag, + const ser_context&, + std::error_code& ec) override + { + if (++level_ != 1) + { + ec = conv_errc::not_vector; + return false; + } + if (size > 0) + { + reserve_storage(typename std::integral_constant::value>::type(), v_, size); + } + return true; + } + + bool visit_end_array(const ser_context&, + std::error_code& ec) override + { + if (level_ != 1) + { + ec = conv_errc::not_vector; + return false; + } + return false; + } + + bool visit_uint64(uint64_t value, + semantic_tag, + const ser_context&, + std::error_code&) override + { + v_.push_back(static_cast(value)); + return true; + } + + bool visit_int64(int64_t value, + semantic_tag, + const ser_context&, + std::error_code&) override + { + v_.push_back(static_cast(value)); + return true; + } + + bool visit_half(uint16_t value, + semantic_tag, + const ser_context&, + std::error_code&) override + { + return visit_half_(typename std::integral_constant::value>::type(), value); + } + + bool visit_half_(std::true_type, uint16_t value) + { + v_.push_back(static_cast(value)); + return true; + } + + bool visit_half_(std::false_type, uint16_t value) + { + v_.push_back(static_cast(binary::decode_half(value))); + return true; + } + + bool visit_double(double value, + semantic_tag, + const ser_context&, + std::error_code&) override + { + v_.push_back(static_cast(value)); + return true; + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag, + const ser_context&, + std::error_code&) override + { + v_ = std::vector(data.begin(),data.end()); + return false; + } + + static + void reserve_storage(std::true_type, T& v, std::size_t new_cap) + { + v.reserve(new_cap); + } + + static + void reserve_storage(std::false_type, T&, std::size_t) + { + } + }; + + template + struct decode_traits::value && + extension_traits::is_array_like::value && + extension_traits::is_back_insertable_byte_container::value && + extension_traits::is_typed_array::value + >::type> + { + using value_type = typename T::value_type; + + template + static T decode(basic_staj_cursor& cursor, + json_decoder&, + std::error_code& ec) + { + cursor.array_expected(ec); + if (ec) + { + return T{}; + } + switch (cursor.current().event_type()) + { + case staj_event_type::byte_string_value: + { + auto bytes = cursor.current().template get(ec); + if (!ec) + { + T v; + if (cursor.current().size() > 0) + { + reserve_storage(typename std::integral_constant::value>::type(), v, cursor.current().size()); + } + for (auto ch : bytes) + { + v.push_back(static_cast(ch)); + } + cursor.next(ec); + return v; + } + else + { + return T{}; + } + } + case staj_event_type::begin_array: + { + T v; + if (cursor.current().size() > 0) + { + reserve_storage(typename std::integral_constant::value>::type(), v, cursor.current().size()); + } + typed_array_visitor visitor(v); + cursor.read_to(visitor, ec); + return v; + } + default: + { + ec = conv_errc::not_vector; + return T{}; + } + } + } + + static void reserve_storage(std::true_type, T& v, std::size_t new_cap) + { + v.reserve(new_cap); + } + + static void reserve_storage(std::false_type, T&, std::size_t) + { + } + }; + + template + struct decode_traits::value && + extension_traits::is_array_like::value && + extension_traits::is_back_insertable::value && + !extension_traits::is_back_insertable_byte_container::value && + extension_traits::is_typed_array::value + >::type> + { + using value_type = typename T::value_type; + + template + static T decode(basic_staj_cursor& cursor, + json_decoder&, + std::error_code& ec) + { + cursor.array_expected(ec); + if (ec) + { + return T{}; + } + switch (cursor.current().event_type()) + { + case staj_event_type::begin_array: + { + T v; + if (cursor.current().size() > 0) + { + reserve_storage(typename std::integral_constant::value>::type(), v, cursor.current().size()); + } + typed_array_visitor visitor(v); + cursor.read_to(visitor, ec); + return v; + } + default: + { + ec = conv_errc::not_vector; + return T{}; + } + } + } + + static void reserve_storage(std::true_type, T& v, std::size_t new_cap) + { + v.reserve(new_cap); + } + + static void reserve_storage(std::false_type, T&, std::size_t) + { + } + }; + + // set like + template + struct decode_traits::value && + extension_traits::is_array_like::value && + !extension_traits::is_back_insertable::value && + extension_traits::is_insertable::value + >::type> + { + using value_type = typename T::value_type; + + template + static T decode(basic_staj_cursor& cursor, + json_decoder& decoder, + std::error_code& ec) + { + T v; + + cursor.array_expected(ec); + if (ec) + { + return T{}; + } + if (cursor.current().event_type() != staj_event_type::begin_array) + { + ec = conv_errc::not_vector; + return v; + } + if (cursor.current().size() > 0) + { + reserve_storage(typename std::integral_constant::value>::type(), v, cursor.current().size()); + } + cursor.next(ec); + while (cursor.current().event_type() != staj_event_type::end_array && !ec) + { + v.insert(decode_traits::decode(cursor, decoder, ec)); + if (ec) {return T{};} + cursor.next(ec); + if (ec) {return T{};} + } + return v; + } + + static void reserve_storage(std::true_type, T& v, std::size_t new_cap) + { + v.reserve(new_cap); + } + + static void reserve_storage(std::false_type, T&, std::size_t) + { + } + }; + + // std::array + + template + struct decode_traits,CharT> + { + using value_type = typename std::array::value_type; + + template + static std::array decode(basic_staj_cursor& cursor, + json_decoder& decoder, + std::error_code& ec) + { + std::array v; + cursor.array_expected(ec); + if (ec) + { + v.fill(T()); + return v; + } + v.fill(T{}); + if (cursor.current().event_type() != staj_event_type::begin_array) + { + ec = conv_errc::not_vector; + return v; + } + cursor.next(ec); + for (std::size_t i = 0; i < N && cursor.current().event_type() != staj_event_type::end_array && !ec; ++i) + { + v[i] = decode_traits::decode(cursor, decoder, ec); + if (ec) {return v;} + cursor.next(ec); + if (ec) {return v;} + } + return v; + } + }; + + // map like + + template + struct decode_traits::value && + extension_traits::is_map_like::value && + extension_traits::is_constructible_from_const_pointer_and_size::value + >::type> + { + using mapped_type = typename T::mapped_type; + using value_type = typename T::value_type; + using key_type = typename T::key_type; + + template + static T decode(basic_staj_cursor& cursor, + json_decoder& decoder, + std::error_code& ec) + { + T val; + if (cursor.current().event_type() != staj_event_type::begin_object) + { + ec = conv_errc::not_map; + return val; + } + if (cursor.current().size() > 0) + { + reserve_storage(typename std::integral_constant::value>::type(), val, cursor.current().size()); + } + cursor.next(ec); + + while (cursor.current().event_type() != staj_event_type::end_object && !ec) + { + if (cursor.current().event_type() != staj_event_type::key) + { + ec = json_errc::expected_key; + return val; + } + auto key = cursor.current().template get(ec); + if (ec) return val; + cursor.next(ec); + if (ec) return val; + val.emplace(std::move(key),decode_traits::decode(cursor, decoder, ec)); + if (ec) {return val;} + cursor.next(ec); + if (ec) {return val;} + } + return val; + } + + static void reserve_storage(std::true_type, T& v, std::size_t new_cap) + { + v.reserve(new_cap); + } + + static void reserve_storage(std::false_type, T&, std::size_t) + { + } + }; + + template + struct decode_traits::value && + extension_traits::is_map_like::value && + std::is_integral::value + >::type> + { + using mapped_type = typename T::mapped_type; + using value_type = typename T::value_type; + using key_type = typename T::key_type; + + template + static T decode(basic_staj_cursor& cursor, + json_decoder& decoder, + std::error_code& ec) + { + T val; + if (cursor.current().event_type() != staj_event_type::begin_object) + { + ec = conv_errc::not_map; + return val; + } + if (cursor.current().size() > 0) + { + reserve_storage(typename std::integral_constant::value>::type(), val, cursor.current().size()); + } + cursor.next(ec); + + while (cursor.current().event_type() != staj_event_type::end_object && !ec) + { + if (cursor.current().event_type() != staj_event_type::key) + { + ec = json_errc::expected_key; + return val; + } + auto s = cursor.current().template get>(ec); + if (ec) return val; + key_type n{0}; + auto r = jsoncons::detail::to_integer(s.data(), s.size(), n); + if (r.ec != jsoncons::detail::to_integer_errc()) + { + ec = json_errc::invalid_number; + return val; + } + cursor.next(ec); + if (ec) return val; + val.emplace(n, decode_traits::decode(cursor, decoder, ec)); + if (ec) {return val;} + cursor.next(ec); + if (ec) {return val;} + } + return val; + } + + static void reserve_storage(std::true_type, T& v, std::size_t new_cap) + { + v.reserve(new_cap); + } + + static void reserve_storage(std::false_type, T&, std::size_t) + { + } + }; + +} // jsoncons + +#endif + diff --git a/third_party/jsoncons/detail/endian.hpp b/third_party/jsoncons/detail/endian.hpp new file mode 100644 index 0000000000..619f098670 --- /dev/null +++ b/third_party/jsoncons/detail/endian.hpp @@ -0,0 +1,44 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DETAIL_ENDIAN_HPP +#define JSONCONS_DETAIL_ENDIAN_HPP + +#if defined(__sun) +# include +#endif + +namespace jsoncons { +namespace detail { + + enum class endian + { + #if defined(_MSC_VER) + // MSVC, which implies Windows, which implies little-endian + little = 0, + big = 1, + native = little + #elif defined(__ORDER_LITTLE_ENDIAN__) && defined(__ORDER_BIG_ENDIAN__) && defined(__BYTE_ORDER__) + little = __ORDER_LITTLE_ENDIAN__, + big = __ORDER_BIG_ENDIAN__, + native = __BYTE_ORDER__ + #elif defined(_BIG_ENDIAN) && !defined(_LITTLE_ENDIAN) + little = 0, + big = 1, + native = big + #elif !defined(_BIG_ENDIAN) && defined(_LITTLE_ENDIAN) + little = 0, + big = 1, + native = little + #else + #error "Unable to determine byte order!" + #endif + }; + +} // namespace detail +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/detail/grisu3.hpp b/third_party/jsoncons/detail/grisu3.hpp new file mode 100644 index 0000000000..771cf1d3b7 --- /dev/null +++ b/third_party/jsoncons/detail/grisu3.hpp @@ -0,0 +1,312 @@ +/* +Implements the Grisu3 algorithm for printing floating-point numbers. + +Follows Florian Loitsch's grisu3_59_56 implementation, available at +http://florian.loitsch.com/publications, in bench.tar.gz, with +minor modifications. +*/ + +/* + Copyright (c) 2009 Florian Loitsch + + Permission is hereby granted, free of charge, to any person + obtaining a copy of this software and associated documentation + files (the "Software"), to deal in the Software without + restriction, including without limitation the rights to use, + copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the + Software is furnished to do so, subject to the following + conditions: + + The above copyright notice and this permission notice shall be + included in all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR + OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef JSONCONS_GRISU3_HPP +#define JSONCONS_GRISU3_HPP + +#pragma once +#include +#include +#include +#include +#include +#include // std::memmove +#include + +namespace jsoncons { namespace detail { + +// diy_fp + +typedef struct diy_fp_t { + uint64_t f; + int e; +} diy_fp_t; + +inline +diy_fp_t minus(diy_fp_t x, diy_fp_t y) +{ + assert(x.e == y.e); + assert(x.f >= y.f); + diy_fp_t r = { x.f = x.f - y.f, x.e }; + return r; +} + +inline +diy_fp_t multiply(diy_fp_t x, diy_fp_t y) +{ + uint64_t a, b, c, d, ac, bc, ad, bd, tmp; + diy_fp_t r; uint64_t M32 = 0xFFFFFFFF; + a = x.f >> 32; b = x.f & M32; + c = y.f >> 32; d = y.f & M32; + ac = a * c; bc = b * c; ad = a * d; bd = b * d; + tmp = (bd >> 32) + (ad & M32) + (bc & M32); + tmp += 1U << 31; /// mult_round + r.f = ac + (ad >> 32) + (bc >> 32) + (tmp >> 32); + r.e = x.e + y.e + 64; + return r; +} + +// k_comp + +inline +int k_comp(int e, int alpha, int /*gamma*/) +{ + constexpr double d_1_log2_10 = 0.30102999566398114; // 1 / lg(10) + int x = alpha - e + 63; + return static_cast(std::ceil(x * d_1_log2_10)); +} + +// powers_ten_round64 + +constexpr int diy_significand_size = 64; + +static constexpr uint64_t powers_ten[] = { 0xbf29dcaba82fdeae, 0xeef453d6923bd65a, 0x9558b4661b6565f8, 0xbaaee17fa23ebf76, 0xe95a99df8ace6f54, 0x91d8a02bb6c10594, 0xb64ec836a47146fa, 0xe3e27a444d8d98b8, 0x8e6d8c6ab0787f73, 0xb208ef855c969f50, 0xde8b2b66b3bc4724, 0x8b16fb203055ac76, 0xaddcb9e83c6b1794, 0xd953e8624b85dd79, 0x87d4713d6f33aa6c, 0xa9c98d8ccb009506, 0xd43bf0effdc0ba48, 0x84a57695fe98746d, 0xa5ced43b7e3e9188, 0xcf42894a5dce35ea, 0x818995ce7aa0e1b2, 0xa1ebfb4219491a1f, 0xca66fa129f9b60a7, 0xfd00b897478238d1, 0x9e20735e8cb16382, 0xc5a890362fddbc63, 0xf712b443bbd52b7c, 0x9a6bb0aa55653b2d, 0xc1069cd4eabe89f9, 0xf148440a256e2c77, 0x96cd2a865764dbca, 0xbc807527ed3e12bd, 0xeba09271e88d976c, 0x93445b8731587ea3, 0xb8157268fdae9e4c, 0xe61acf033d1a45df, 0x8fd0c16206306bac, 0xb3c4f1ba87bc8697, 0xe0b62e2929aba83c, 0x8c71dcd9ba0b4926, 0xaf8e5410288e1b6f, 0xdb71e91432b1a24b, 0x892731ac9faf056f, 0xab70fe17c79ac6ca, 0xd64d3d9db981787d, 0x85f0468293f0eb4e, 0xa76c582338ed2622, 0xd1476e2c07286faa, 0x82cca4db847945ca, 0xa37fce126597973d, 0xcc5fc196fefd7d0c, 0xff77b1fcbebcdc4f, 0x9faacf3df73609b1, 0xc795830d75038c1e, 0xf97ae3d0d2446f25, 0x9becce62836ac577, 0xc2e801fb244576d5, 0xf3a20279ed56d48a, 0x9845418c345644d7, 0xbe5691ef416bd60c, 0xedec366b11c6cb8f, 0x94b3a202eb1c3f39, 0xb9e08a83a5e34f08, 0xe858ad248f5c22ca, 0x91376c36d99995be, 0xb58547448ffffb2e, 0xe2e69915b3fff9f9, 0x8dd01fad907ffc3c, 0xb1442798f49ffb4b, 0xdd95317f31c7fa1d, 0x8a7d3eef7f1cfc52, 0xad1c8eab5ee43b67, 0xd863b256369d4a41, 0x873e4f75e2224e68, 0xa90de3535aaae202, 0xd3515c2831559a83, 0x8412d9991ed58092, 0xa5178fff668ae0b6, 0xce5d73ff402d98e4, 0x80fa687f881c7f8e, 0xa139029f6a239f72, 0xc987434744ac874f, 0xfbe9141915d7a922, 0x9d71ac8fada6c9b5, 0xc4ce17b399107c23, 0xf6019da07f549b2b, 0x99c102844f94e0fb, 0xc0314325637a193a, 0xf03d93eebc589f88, 0x96267c7535b763b5, 0xbbb01b9283253ca3, 0xea9c227723ee8bcb, 0x92a1958a7675175f, 0xb749faed14125d37, 0xe51c79a85916f485, 0x8f31cc0937ae58d3, 0xb2fe3f0b8599ef08, 0xdfbdcece67006ac9, 0x8bd6a141006042be, 0xaecc49914078536d, 0xda7f5bf590966849, 0x888f99797a5e012d, 0xaab37fd7d8f58179, 0xd5605fcdcf32e1d7, 0x855c3be0a17fcd26, 0xa6b34ad8c9dfc070, 0xd0601d8efc57b08c, 0x823c12795db6ce57, 0xa2cb1717b52481ed, 0xcb7ddcdda26da269, 0xfe5d54150b090b03, 0x9efa548d26e5a6e2, 0xc6b8e9b0709f109a, 0xf867241c8cc6d4c1, 0x9b407691d7fc44f8, 0xc21094364dfb5637, 0xf294b943e17a2bc4, 0x979cf3ca6cec5b5b, 0xbd8430bd08277231, 0xece53cec4a314ebe, 0x940f4613ae5ed137, 0xb913179899f68584, 0xe757dd7ec07426e5, 0x9096ea6f3848984f, 0xb4bca50b065abe63, 0xe1ebce4dc7f16dfc, 0x8d3360f09cf6e4bd, 0xb080392cc4349ded, 0xdca04777f541c568, 0x89e42caaf9491b61, 0xac5d37d5b79b6239, 0xd77485cb25823ac7, 0x86a8d39ef77164bd, 0xa8530886b54dbdec, 0xd267caa862a12d67, 0x8380dea93da4bc60, 0xa46116538d0deb78, 0xcd795be870516656, 0x806bd9714632dff6, 0xa086cfcd97bf97f4, 0xc8a883c0fdaf7df0, 0xfad2a4b13d1b5d6c, 0x9cc3a6eec6311a64, 0xc3f490aa77bd60fd, 0xf4f1b4d515acb93c, 0x991711052d8bf3c5, 0xbf5cd54678eef0b7, 0xef340a98172aace5, 0x9580869f0e7aac0f, 0xbae0a846d2195713, 0xe998d258869facd7, 0x91ff83775423cc06, 0xb67f6455292cbf08, 0xe41f3d6a7377eeca, 0x8e938662882af53e, 0xb23867fb2a35b28e, 0xdec681f9f4c31f31, 0x8b3c113c38f9f37f, 0xae0b158b4738705f, 0xd98ddaee19068c76, 0x87f8a8d4cfa417ca, 0xa9f6d30a038d1dbc, 0xd47487cc8470652b, 0x84c8d4dfd2c63f3b, 0xa5fb0a17c777cf0a, 0xcf79cc9db955c2cc, 0x81ac1fe293d599c0, 0xa21727db38cb0030, 0xca9cf1d206fdc03c, 0xfd442e4688bd304b, 0x9e4a9cec15763e2f, 0xc5dd44271ad3cdba, 0xf7549530e188c129, 0x9a94dd3e8cf578ba, 0xc13a148e3032d6e8, 0xf18899b1bc3f8ca2, 0x96f5600f15a7b7e5, 0xbcb2b812db11a5de, 0xebdf661791d60f56, 0x936b9fcebb25c996, 0xb84687c269ef3bfb, 0xe65829b3046b0afa, 0x8ff71a0fe2c2e6dc, 0xb3f4e093db73a093, 0xe0f218b8d25088b8, 0x8c974f7383725573, 0xafbd2350644eead0, 0xdbac6c247d62a584, 0x894bc396ce5da772, 0xab9eb47c81f5114f, 0xd686619ba27255a3, 0x8613fd0145877586, 0xa798fc4196e952e7, 0xd17f3b51fca3a7a1, 0x82ef85133de648c5, 0xa3ab66580d5fdaf6, 0xcc963fee10b7d1b3, 0xffbbcfe994e5c620, 0x9fd561f1fd0f9bd4, 0xc7caba6e7c5382c9, 0xf9bd690a1b68637b, 0x9c1661a651213e2d, 0xc31bfa0fe5698db8, 0xf3e2f893dec3f126, 0x986ddb5c6b3a76b8, 0xbe89523386091466, 0xee2ba6c0678b597f, 0x94db483840b717f0, 0xba121a4650e4ddec, 0xe896a0d7e51e1566, 0x915e2486ef32cd60, 0xb5b5ada8aaff80b8, 0xe3231912d5bf60e6, 0x8df5efabc5979c90, 0xb1736b96b6fd83b4, 0xddd0467c64bce4a1, 0x8aa22c0dbef60ee4, 0xad4ab7112eb3929e, 0xd89d64d57a607745, 0x87625f056c7c4a8b, 0xa93af6c6c79b5d2e, 0xd389b47879823479, 0x843610cb4bf160cc, 0xa54394fe1eedb8ff, 0xce947a3da6a9273e, 0x811ccc668829b887, 0xa163ff802a3426a9, 0xc9bcff6034c13053, 0xfc2c3f3841f17c68, 0x9d9ba7832936edc1, 0xc5029163f384a931, 0xf64335bcf065d37d, 0x99ea0196163fa42e, 0xc06481fb9bcf8d3a, 0xf07da27a82c37088, 0x964e858c91ba2655, 0xbbe226efb628afeb, 0xeadab0aba3b2dbe5, 0x92c8ae6b464fc96f, 0xb77ada0617e3bbcb, 0xe55990879ddcaabe, 0x8f57fa54c2a9eab7, 0xb32df8e9f3546564, 0xdff9772470297ebd, 0x8bfbea76c619ef36, 0xaefae51477a06b04, 0xdab99e59958885c5, 0x88b402f7fd75539b, 0xaae103b5fcd2a882, 0xd59944a37c0752a2, 0x857fcae62d8493a5, 0xa6dfbd9fb8e5b88f, 0xd097ad07a71f26b2, 0x825ecc24c8737830, 0xa2f67f2dfa90563b, 0xcbb41ef979346bca, 0xfea126b7d78186bd, 0x9f24b832e6b0f436, 0xc6ede63fa05d3144, 0xf8a95fcf88747d94, 0x9b69dbe1b548ce7d, 0xc24452da229b021c, 0xf2d56790ab41c2a3, 0x97c560ba6b0919a6, 0xbdb6b8e905cb600f, 0xed246723473e3813, 0x9436c0760c86e30c, 0xb94470938fa89bcf, 0xe7958cb87392c2c3, 0x90bd77f3483bb9ba, 0xb4ecd5f01a4aa828, 0xe2280b6c20dd5232, 0x8d590723948a535f, 0xb0af48ec79ace837, 0xdcdb1b2798182245, 0x8a08f0f8bf0f156b, 0xac8b2d36eed2dac6, 0xd7adf884aa879177, 0x86ccbb52ea94baeb, 0xa87fea27a539e9a5, 0xd29fe4b18e88640f, 0x83a3eeeef9153e89, 0xa48ceaaab75a8e2b, 0xcdb02555653131b6, 0x808e17555f3ebf12, 0xa0b19d2ab70e6ed6, 0xc8de047564d20a8c, 0xfb158592be068d2f, 0x9ced737bb6c4183d, 0xc428d05aa4751e4d, 0xf53304714d9265e0, 0x993fe2c6d07b7fac, 0xbf8fdb78849a5f97, 0xef73d256a5c0f77d, 0x95a8637627989aae, 0xbb127c53b17ec159, 0xe9d71b689dde71b0, 0x9226712162ab070e, 0xb6b00d69bb55c8d1, 0xe45c10c42a2b3b06, 0x8eb98a7a9a5b04e3, 0xb267ed1940f1c61c, 0xdf01e85f912e37a3, 0x8b61313bbabce2c6, 0xae397d8aa96c1b78, 0xd9c7dced53c72256, 0x881cea14545c7575, 0xaa242499697392d3, 0xd4ad2dbfc3d07788, 0x84ec3c97da624ab5, 0xa6274bbdd0fadd62, 0xcfb11ead453994ba, 0x81ceb32c4b43fcf5, 0xa2425ff75e14fc32, 0xcad2f7f5359a3b3e, 0xfd87b5f28300ca0e, 0x9e74d1b791e07e48, 0xc612062576589ddb, 0xf79687aed3eec551, 0x9abe14cd44753b53, 0xc16d9a0095928a27, 0xf1c90080baf72cb1, 0x971da05074da7bef, 0xbce5086492111aeb, 0xec1e4a7db69561a5, 0x9392ee8e921d5d07, 0xb877aa3236a4b449, 0xe69594bec44de15b, 0x901d7cf73ab0acd9, 0xb424dc35095cd80f, 0xe12e13424bb40e13, 0x8cbccc096f5088cc, 0xafebff0bcb24aaff, 0xdbe6fecebdedd5bf, 0x89705f4136b4a597, 0xabcc77118461cefd, 0xd6bf94d5e57a42bc, 0x8637bd05af6c69b6, 0xa7c5ac471b478423, 0xd1b71758e219652c, 0x83126e978d4fdf3b, 0xa3d70a3d70a3d70a, 0xcccccccccccccccd, 0x8000000000000000, 0xa000000000000000, 0xc800000000000000, 0xfa00000000000000, 0x9c40000000000000, 0xc350000000000000, 0xf424000000000000, 0x9896800000000000, 0xbebc200000000000, 0xee6b280000000000, 0x9502f90000000000, 0xba43b74000000000, 0xe8d4a51000000000, 0x9184e72a00000000, 0xb5e620f480000000, 0xe35fa931a0000000, 0x8e1bc9bf04000000, 0xb1a2bc2ec5000000, 0xde0b6b3a76400000, 0x8ac7230489e80000, 0xad78ebc5ac620000, 0xd8d726b7177a8000, 0x878678326eac9000, 0xa968163f0a57b400, 0xd3c21bcecceda100, 0x84595161401484a0, 0xa56fa5b99019a5c8, 0xcecb8f27f4200f3a, 0x813f3978f8940984, 0xa18f07d736b90be5, 0xc9f2c9cd04674edf, 0xfc6f7c4045812296, 0x9dc5ada82b70b59e, 0xc5371912364ce305, 0xf684df56c3e01bc7, 0x9a130b963a6c115c, 0xc097ce7bc90715b3, 0xf0bdc21abb48db20, 0x96769950b50d88f4, 0xbc143fa4e250eb31, 0xeb194f8e1ae525fd, 0x92efd1b8d0cf37be, 0xb7abc627050305ae, 0xe596b7b0c643c719, 0x8f7e32ce7bea5c70, 0xb35dbf821ae4f38c, 0xe0352f62a19e306f, 0x8c213d9da502de45, 0xaf298d050e4395d7, 0xdaf3f04651d47b4c, 0x88d8762bf324cd10, 0xab0e93b6efee0054, 0xd5d238a4abe98068, 0x85a36366eb71f041, 0xa70c3c40a64e6c52, 0xd0cf4b50cfe20766, 0x82818f1281ed44a0, 0xa321f2d7226895c8, 0xcbea6f8ceb02bb3a, 0xfee50b7025c36a08, 0x9f4f2726179a2245, 0xc722f0ef9d80aad6, 0xf8ebad2b84e0d58c, 0x9b934c3b330c8577, 0xc2781f49ffcfa6d5, 0xf316271c7fc3908b, 0x97edd871cfda3a57, 0xbde94e8e43d0c8ec, 0xed63a231d4c4fb27, 0x945e455f24fb1cf9, 0xb975d6b6ee39e437, 0xe7d34c64a9c85d44, 0x90e40fbeea1d3a4b, 0xb51d13aea4a488dd, 0xe264589a4dcdab15, 0x8d7eb76070a08aed, 0xb0de65388cc8ada8, 0xdd15fe86affad912, 0x8a2dbf142dfcc7ab, 0xacb92ed9397bf996, 0xd7e77a8f87daf7fc, 0x86f0ac99b4e8dafd, 0xa8acd7c0222311bd, 0xd2d80db02aabd62c, 0x83c7088e1aab65db, 0xa4b8cab1a1563f52, 0xcde6fd5e09abcf27, 0x80b05e5ac60b6178, 0xa0dc75f1778e39d6, 0xc913936dd571c84c, 0xfb5878494ace3a5f, 0x9d174b2dcec0e47b, 0xc45d1df942711d9a, 0xf5746577930d6501, 0x9968bf6abbe85f20, 0xbfc2ef456ae276e9, 0xefb3ab16c59b14a3, 0x95d04aee3b80ece6, 0xbb445da9ca61281f, 0xea1575143cf97227, 0x924d692ca61be758, 0xb6e0c377cfa2e12e, 0xe498f455c38b997a, 0x8edf98b59a373fec, 0xb2977ee300c50fe7, 0xdf3d5e9bc0f653e1, 0x8b865b215899f46d, 0xae67f1e9aec07188, 0xda01ee641a708dea, 0x884134fe908658b2, 0xaa51823e34a7eedf, 0xd4e5e2cdc1d1ea96, 0x850fadc09923329e, 0xa6539930bf6bff46, 0xcfe87f7cef46ff17, 0x81f14fae158c5f6e, 0xa26da3999aef774a, 0xcb090c8001ab551c, 0xfdcb4fa002162a63, 0x9e9f11c4014dda7e, 0xc646d63501a1511e, 0xf7d88bc24209a565, 0x9ae757596946075f, 0xc1a12d2fc3978937, 0xf209787bb47d6b85, 0x9745eb4d50ce6333, 0xbd176620a501fc00, 0xec5d3fa8ce427b00, 0x93ba47c980e98ce0, 0xb8a8d9bbe123f018, 0xe6d3102ad96cec1e, 0x9043ea1ac7e41393, 0xb454e4a179dd1877, 0xe16a1dc9d8545e95, 0x8ce2529e2734bb1d, 0xb01ae745b101e9e4, 0xdc21a1171d42645d, 0x899504ae72497eba, 0xabfa45da0edbde69, 0xd6f8d7509292d603, 0x865b86925b9bc5c2, 0xa7f26836f282b733, 0xd1ef0244af2364ff, 0x8335616aed761f1f, 0xa402b9c5a8d3a6e7, 0xcd036837130890a1, 0x802221226be55a65, 0xa02aa96b06deb0fe, 0xc83553c5c8965d3d, 0xfa42a8b73abbf48d, 0x9c69a97284b578d8, 0xc38413cf25e2d70e, 0xf46518c2ef5b8cd1, 0x98bf2f79d5993803, 0xbeeefb584aff8604, 0xeeaaba2e5dbf6785, 0x952ab45cfa97a0b3, 0xba756174393d88e0, 0xe912b9d1478ceb17, 0x91abb422ccb812ef, 0xb616a12b7fe617aa, 0xe39c49765fdf9d95, 0x8e41ade9fbebc27d, 0xb1d219647ae6b31c, 0xde469fbd99a05fe3, 0x8aec23d680043bee, 0xada72ccc20054aea, 0xd910f7ff28069da4, 0x87aa9aff79042287, 0xa99541bf57452b28, 0xd3fa922f2d1675f2, 0x847c9b5d7c2e09b7, 0xa59bc234db398c25, 0xcf02b2c21207ef2f, 0x8161afb94b44f57d, 0xa1ba1ba79e1632dc, 0xca28a291859bbf93, 0xfcb2cb35e702af78, 0x9defbf01b061adab, 0xc56baec21c7a1916, 0xf6c69a72a3989f5c, 0x9a3c2087a63f6399, 0xc0cb28a98fcf3c80, 0xf0fdf2d3f3c30b9f, 0x969eb7c47859e744, 0xbc4665b596706115, 0xeb57ff22fc0c795a, 0x9316ff75dd87cbd8, 0xb7dcbf5354e9bece, 0xe5d3ef282a242e82, 0x8fa475791a569d11, 0xb38d92d760ec4455, 0xe070f78d3927556b, 0x8c469ab843b89563, 0xaf58416654a6babb, 0xdb2e51bfe9d0696a, 0x88fcf317f22241e2, 0xab3c2fddeeaad25b, 0xd60b3bd56a5586f2, 0x85c7056562757457, 0xa738c6bebb12d16d, 0xd106f86e69d785c8, 0x82a45b450226b39d, 0xa34d721642b06084, 0xcc20ce9bd35c78a5, 0xff290242c83396ce, 0x9f79a169bd203e41, 0xc75809c42c684dd1, 0xf92e0c3537826146, 0x9bbcc7a142b17ccc, 0xc2abf989935ddbfe, 0xf356f7ebf83552fe, 0x98165af37b2153df, 0xbe1bf1b059e9a8d6, 0xeda2ee1c7064130c, 0x9485d4d1c63e8be8, 0xb9a74a0637ce2ee1, 0xe8111c87c5c1ba9a, 0x910ab1d4db9914a0, 0xb54d5e4a127f59c8, 0xe2a0b5dc971f303a, 0x8da471a9de737e24, 0xb10d8e1456105dad, 0xdd50f1996b947519, 0x8a5296ffe33cc930, 0xace73cbfdc0bfb7b, 0xd8210befd30efa5a, 0x8714a775e3e95c78, 0xa8d9d1535ce3b396, 0xd31045a8341ca07c, 0x83ea2b892091e44e, 0xa4e4b66b68b65d61, 0xce1de40642e3f4b9, 0x80d2ae83e9ce78f4, 0xa1075a24e4421731, 0xc94930ae1d529cfd, 0xfb9b7cd9a4a7443c, 0x9d412e0806e88aa6, 0xc491798a08a2ad4f, 0xf5b5d7ec8acb58a3, 0x9991a6f3d6bf1766, 0xbff610b0cc6edd3f, 0xeff394dcff8a948f, 0x95f83d0a1fb69cd9, 0xbb764c4ca7a44410, 0xea53df5fd18d5514, 0x92746b9be2f8552c, 0xb7118682dbb66a77, 0xe4d5e82392a40515, 0x8f05b1163ba6832d, 0xb2c71d5bca9023f8, 0xdf78e4b2bd342cf7, 0x8bab8eefb6409c1a, 0xae9672aba3d0c321, 0xda3c0f568cc4f3e9, 0x8865899617fb1871, 0xaa7eebfb9df9de8e, 0xd51ea6fa85785631, 0x8533285c936b35df, 0xa67ff273b8460357, 0xd01fef10a657842c, 0x8213f56a67f6b29c, 0xa298f2c501f45f43, 0xcb3f2f7642717713, 0xfe0efb53d30dd4d8, 0x9ec95d1463e8a507, 0xc67bb4597ce2ce49, 0xf81aa16fdc1b81db, 0x9b10a4e5e9913129, 0xc1d4ce1f63f57d73, 0xf24a01a73cf2dcd0, 0x976e41088617ca02, 0xbd49d14aa79dbc82, 0xec9c459d51852ba3, 0x93e1ab8252f33b46, 0xb8da1662e7b00a17, 0xe7109bfba19c0c9d, 0x906a617d450187e2, 0xb484f9dc9641e9db, 0xe1a63853bbd26451, 0x8d07e33455637eb3, 0xb049dc016abc5e60, 0xdc5c5301c56b75f7, 0x89b9b3e11b6329bb, 0xac2820d9623bf429, 0xd732290fbacaf134, 0x867f59a9d4bed6c0, 0xa81f301449ee8c70, 0xd226fc195c6a2f8c, 0x83585d8fd9c25db8, 0xa42e74f3d032f526, 0xcd3a1230c43fb26f, 0x80444b5e7aa7cf85, 0xa0555e361951c367, 0xc86ab5c39fa63441, 0xfa856334878fc151, 0x9c935e00d4b9d8d2, 0xc3b8358109e84f07, 0xf4a642e14c6262c9, 0x98e7e9cccfbd7dbe, 0xbf21e44003acdd2d, 0xeeea5d5004981478, 0x95527a5202df0ccb, 0xbaa718e68396cffe, 0xe950df20247c83fd, 0x91d28b7416cdd27e, 0xb6472e511c81471e, 0xe3d8f9e563a198e5, 0x8e679c2f5e44ff8f, 0xb201833b35d63f73, 0xde81e40a034bcf50, 0x8b112e86420f6192, 0xadd57a27d29339f6, 0xd94ad8b1c7380874, 0x87cec76f1c830549, 0xa9c2794ae3a3c69b, 0xd433179d9c8cb841, 0x849feec281d7f329, 0xa5c7ea73224deff3, 0xcf39e50feae16bf0, 0x81842f29f2cce376, 0xa1e53af46f801c53, 0xca5e89b18b602368, 0xfcf62c1dee382c42, 0x9e19db92b4e31ba9, 0xc5a05277621be294, 0xf70867153aa2db39, 0x9a65406d44a5c903, 0xc0fe908895cf3b44, 0xf13e34aabb430a15, 0x96c6e0eab509e64d, 0xbc789925624c5fe1, 0xeb96bf6ebadf77d9, 0x933e37a534cbaae8, 0xb80dc58e81fe95a1, 0xe61136f2227e3b0a, 0x8fcac257558ee4e6, 0xb3bd72ed2af29e20, 0xe0accfa875af45a8, 0x8c6c01c9498d8b89, 0xaf87023b9bf0ee6b, 0xdb68c2ca82ed2a06, 0x892179be91d43a44, 0xab69d82e364948d4 }; +static constexpr int powers_ten_e[] = { -1203, -1200, -1196, -1193, -1190, -1186, -1183, -1180, -1176, -1173, -1170, -1166, -1163, -1160, -1156, -1153, -1150, -1146, -1143, -1140, -1136, -1133, -1130, -1127, -1123, -1120, -1117, -1113, -1110, -1107, -1103, -1100, -1097, -1093, -1090, -1087, -1083, -1080, -1077, -1073, -1070, -1067, -1063, -1060, -1057, -1053, -1050, -1047, -1043, -1040, -1037, -1034, -1030, -1027, -1024, -1020, -1017, -1014, -1010, -1007, -1004, -1000, -997, -994, -990, -987, -984, -980, -977, -974, -970, -967, -964, -960, -957, -954, -950, -947, -944, -940, -937, -934, -931, -927, -924, -921, -917, -914, -911, -907, -904, -901, -897, -894, -891, -887, -884, -881, -877, -874, -871, -867, -864, -861, -857, -854, -851, -847, -844, -841, -838, -834, -831, -828, -824, -821, -818, -814, -811, -808, -804, -801, -798, -794, -791, -788, -784, -781, -778, -774, -771, -768, -764, -761, -758, -754, -751, -748, -744, -741, -738, -735, -731, -728, -725, -721, -718, -715, -711, -708, -705, -701, -698, -695, -691, -688, -685, -681, -678, -675, -671, -668, -665, -661, -658, -655, -651, -648, -645, -642, -638, -635, -632, -628, -625, -622, -618, -615, -612, -608, -605, -602, -598, -595, -592, -588, -585, -582, -578, -575, -572, -568, -565, -562, -558, -555, -552, -549, -545, -542, -539, -535, -532, -529, -525, -522, -519, -515, -512, -509, -505, -502, -499, -495, -492, -489, -485, -482, -479, -475, -472, -469, -465, -462, -459, -455, -452, -449, -446, -442, -439, -436, -432, -429, -426, -422, -419, -416, -412, -409, -406, -402, -399, -396, -392, -389, -386, -382, -379, -376, -372, -369, -366, -362, -359, -356, -353, -349, -346, -343, -339, -336, -333, -329, -326, -323, -319, -316, -313, -309, -306, -303, -299, -296, -293, -289, -286, -283, -279, -276, -273, -269, -266, -263, -259, -256, -253, -250, -246, -243, -240, -236, -233, -230, -226, -223, -220, -216, -213, -210, -206, -203, -200, -196, -193, -190, -186, -183, -180, -176, -173, -170, -166, -163, -160, -157, -153, -150, -147, -143, -140, -137, -133, -130, -127, -123, -120, -117, -113, -110, -107, -103, -100, -97, -93, -90, -87, -83, -80, -77, -73, -70, -67, -63, -60, -57, -54, -50, -47, -44, -40, -37, -34, -30, -27, -24, -20, -17, -14, -10, -7, -4, 0, 3, 6, 10, 13, 16, 20, 23, 26, 30, 33, 36, 39, 43, 46, 49, 53, 56, 59, 63, 66, 69, 73, 76, 79, 83, 86, 89, 93, 96, 99, 103, 106, 109, 113, 116, 119, 123, 126, 129, 132, 136, 139, 142, 146, 149, 152, 156, 159, 162, 166, 169, 172, 176, 179, 182, 186, 189, 192, 196, 199, 202, 206, 209, 212, 216, 219, 222, 226, 229, 232, 235, 239, 242, 245, 249, 252, 255, 259, 262, 265, 269, 272, 275, 279, 282, 285, 289, 292, 295, 299, 302, 305, 309, 312, 315, 319, 322, 325, 328, 332, 335, 338, 342, 345, 348, 352, 355, 358, 362, 365, 368, 372, 375, 378, 382, 385, 388, 392, 395, 398, 402, 405, 408, 412, 415, 418, 422, 425, 428, 431, 435, 438, 441, 445, 448, 451, 455, 458, 461, 465, 468, 471, 475, 478, 481, 485, 488, 491, 495, 498, 501, 505, 508, 511, 515, 518, 521, 524, 528, 531, 534, 538, 541, 544, 548, 551, 554, 558, 561, 564, 568, 571, 574, 578, 581, 584, 588, 591, 594, 598, 601, 604, 608, 611, 614, 617, 621, 624, 627, 631, 634, 637, 641, 644, 647, 651, 654, 657, 661, 664, 667, 671, 674, 677, 681, 684, 687, 691, 694, 697, 701, 704, 707, 711, 714, 717, 720, 724, 727, 730, 734, 737, 740, 744, 747, 750, 754, 757, 760, 764, 767, 770, 774, 777, 780, 784, 787, 790, 794, 797, 800, 804, 807, 810, 813, 817, 820, 823, 827, 830, 833, 837, 840, 843, 847, 850, 853, 857, 860, 863, 867, 870, 873, 877, 880, 883, 887, 890, 893, 897, 900, 903, 907, 910, 913, 916, 920, 923, 926, 930, 933, 936, 940, 943, 946, 950, 953, 956, 960, 963, 966, 970, 973, 976, 980, 983, 986, 990, 993, 996, 1000, 1003, 1006, 1009, 1013, 1016, 1019, 1023, 1026, 1029, 1033, 1036, 1039, 1043, 1046, 1049, 1053, 1056, 1059, 1063, 1066, 1069, 1073, 1076 }; + +inline +diy_fp_t cached_power(int k) +{ + diy_fp_t res; + int index = 343 + k; + res.f = powers_ten[index]; + res.e = powers_ten_e[index]; + return res; +} + +// double + +/* +typedef union { + double d; + uint64_t n; +} converter_t; + +inline +uint64_t double_to_uint64(double d) { converter_t tmp; tmp.d = d; return tmp.n; } + +inline +double uint64_to_double(uint64_t d64) { converter_t tmp; tmp.n = d64; return tmp.d; } +*/ +inline +uint64_t double_to_uint64(double d) {uint64_t d64; std::memcpy(&d64,&d,sizeof(double)); return d64; } + +inline +double uint64_to_double(uint64_t d64) {double d; std::memcpy(&d,&d64,sizeof(double)); return d; } + +constexpr int dp_significand_size = 52; +constexpr int dp_exponent_bias = (0x3FF + dp_significand_size); +constexpr int dp_min_exponent = (-dp_exponent_bias); +constexpr uint64_t dp_exponent_mask = 0x7FF0000000000000; +constexpr uint64_t dp_significand_mask = 0x000FFFFFFFFFFFFF; +constexpr uint64_t dp_hidden_bit = 0x0010000000000000; + +inline +diy_fp_t normalize_diy_fp(diy_fp_t in) +{ + diy_fp_t res = in; + /* Normalize now */ + /* the original number could have been a denormal. */ + while (!(res.f & dp_hidden_bit)) + { + res.f <<= 1; + res.e--; + } + /* do the final shifts in one go. Don't forget the hidden bit (the '-1') */ + res.f <<= (diy_significand_size - dp_significand_size - 1); + res.e = res.e - (diy_significand_size - dp_significand_size - 1); + return res; +} + +inline +diy_fp_t double2diy_fp(double d) +{ + uint64_t d64 = double_to_uint64(d); + int biased_e = (d64 & dp_exponent_mask) >> dp_significand_size; + uint64_t significand = (d64 & dp_significand_mask); + diy_fp_t res; + if (biased_e != 0) + { + res.f = significand + dp_hidden_bit; + res.e = biased_e - dp_exponent_bias; + } + else + { + res.f = significand; + res.e = dp_min_exponent + 1; + } + return res; +} + +inline +diy_fp_t normalize_boundary(diy_fp_t in) +{ + diy_fp_t res = in; + /* Normalize now */ + /* the original number could have been a denormal. */ + while (!(res.f & (dp_hidden_bit << 1))) + { + res.f <<= 1; + res.e--; + } + /* do the final shifts in one go. Don't forget the hidden bit (the '-1') */ + res.f <<= (diy_significand_size - dp_significand_size - 2); + res.e = res.e - (diy_significand_size - dp_significand_size - 2); + return res; +} + +inline +void normalized_boundaries(double d, diy_fp_t *out_m_minus, diy_fp_t *out_m_plus) +{ + diy_fp_t v = double2diy_fp(d); + diy_fp_t pl, mi; + bool significand_is_zero = v.f == dp_hidden_bit; + pl.f = (v.f << 1) + 1; pl.e = v.e - 1; + pl = normalize_boundary(pl); + if (significand_is_zero) + { + mi.f = (v.f << 2) - 1; + mi.e = v.e - 2; + } else + { + mi.f = (v.f << 1) - 1; + mi.e = v.e - 1; + } + int x = mi.e - pl.e; + mi.f <<= x; + mi.e = pl.e; + *out_m_plus = pl; + *out_m_minus = mi; +} + +inline +double random_double() +{ + uint64_t tmp = 0; + int i; + for (i = 0; i < 8; i++) + { + tmp <<= 8; + tmp += rand() % 256; + } + return uint64_to_double(tmp); +} + +// grisu3 + +inline +bool round_weed(char *buffer, int len, + uint64_t wp_W, uint64_t Delta, + uint64_t rest, uint64_t ten_kappa, + uint64_t ulp) +{ + uint64_t wp_Wup = wp_W - ulp; + uint64_t wp_Wdown = wp_W + ulp; + while (rest < wp_Wup && /// round1 + Delta - rest >= ten_kappa && + (rest + ten_kappa < wp_Wup || /// closer + wp_Wup - rest >= rest + ten_kappa - wp_Wup)) + { + buffer[len - 1]--; rest += ten_kappa; + } + if (rest < wp_Wdown && /// round2 + Delta - rest >= ten_kappa && + (rest + ten_kappa < wp_Wdown || + wp_Wdown - rest > rest + ten_kappa - wp_Wdown)) return 0; + return 2 * ulp <= rest && rest <= Delta - 4 * ulp; /// weed +} + +inline +bool digit_gen(diy_fp_t Wm, diy_fp_t W, diy_fp_t Wp, + char *buffer, int *len, int *K) +{ + const uint32_t TEN2 = 100; + + uint32_t div, p1; uint64_t p2, tmp, unit = 1; + int d, kappa; + diy_fp_t one, wp_W, Delta; + Delta = minus(Wp, Wm); Delta.f += 2 * unit; + wp_W = minus(Wp, W); wp_W.f += unit; + one.f = ((uint64_t)1) << -Wp.e; one.e = Wp.e; + p1 = static_cast((Wp.f + 1) >> -one.e); + p2 = (Wp.f + 1) & (one.f - 1); + *len = 0; kappa = 3; div = TEN2; + while (kappa > 0) + { + d = p1 / div; + if (d || *len) buffer[(*len)++] = (char)('0' + d); + p1 %= div; kappa--; + tmp = (((uint64_t)p1) << -one.e) + p2; + if (tmp < Delta.f) + { + *K += kappa; + return round_weed(buffer, *len, wp_W.f, Delta.f, tmp, + ((uint64_t)div) << -one.e, unit); + } + div /= 10; + } + while (1) + { + p2 *= 10; Delta.f *= 10; unit *= 10; + d = static_cast(p2 >> -one.e); + if (d || *len) buffer[(*len)++] = (char)('0' + d); + p2 &= one.f - 1; kappa--; + if (p2 < Delta.f) + { + *K += kappa; + return round_weed(buffer, *len, wp_W.f * unit, Delta.f, p2, + one.f, unit); + } + } +} + +inline +bool grisu3(double v, char *buffer, int *length, int *K) +{ + diy_fp_t w_m, w_p; + int q = 64, alpha = -59, gamma = -56; + normalized_boundaries(v, &w_m, &w_p); + diy_fp_t w = normalize_diy_fp(double2diy_fp(v)); + int mk = k_comp(w_p.e + q, alpha, gamma); + diy_fp_t c_mk = cached_power(mk); + diy_fp_t W = multiply(w, c_mk); + diy_fp_t Wp = multiply(w_p, c_mk); + diy_fp_t Wm = multiply(w_m, c_mk); + *K = -mk; + bool result = digit_gen(Wm, W, Wp, buffer, length, K); + buffer[*length] = 0; + return result; +} + +}} // namespace detail namespace jsoncons + +#endif diff --git a/third_party/jsoncons/detail/heap_string.hpp b/third_party/jsoncons/detail/heap_string.hpp new file mode 100644 index 0000000000..6943c74d64 --- /dev/null +++ b/third_party/jsoncons/detail/heap_string.hpp @@ -0,0 +1,198 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DETAIL_HEAP_STRING_BOX_HPP +#define JSONCONS_DETAIL_HEAP_STRING_BOX_HPP + +#include +#include +#include +#include +#include // std::memcpy +#include // std::allocator +#include + +namespace jsoncons { +namespace detail { + + inline char* + align_up(char* ptr, std::size_t alignment) noexcept + { + return reinterpret_cast(~(alignment - 1) & + (reinterpret_cast(ptr) + alignment - 1)); + } + + template + struct heap_string_base + { + Extra extra_; + Allocator alloc_; + + Allocator& get_allocator() + { + return alloc_; + } + + const Allocator& get_allocator() const + { + return alloc_; + } + + heap_string_base(const Extra& extra, const Allocator& alloc) + : extra_(extra), alloc_(alloc) + { + } + + ~heap_string_base() noexcept = default; + }; + + template + struct heap_string : public heap_string_base + { + using char_type = CharT; + using allocator_type = typename std::allocator_traits::template rebind_alloc; + using allocator_traits_type = std::allocator_traits; + using pointer = typename allocator_traits_type::pointer; + + pointer p_; + std::size_t length_; + uint8_t offset_; + uint8_t align_pad_; + + ~heap_string() noexcept = default; + + const char_type* c_str() const { return extension_traits::to_plain_pointer(p_); } + const char_type* data() const { return extension_traits::to_plain_pointer(p_); } + std::size_t length() const { return length_; } + Extra extra() const { return this->extra_; } + + heap_string(Extra extra, const Allocator& alloc) + : heap_string_base(extra, alloc), p_(nullptr), length_(0), offset_(0) + { + } + + heap_string(const heap_string&) = delete; + heap_string& operator=(const heap_string&) = delete; + + }; + + template + struct jsoncons_aligned_storage + { + struct type + { + alignas(Align) unsigned char data[Len]; + }; + }; + + // From boost 1_71 + template + T launder_cast(U* u) + { + #if defined(__cpp_lib_launder) && __cpp_lib_launder >= 201606 + return std::launder(reinterpret_cast(u)); + #elif defined(__GNUC__) && (__GNUC__ * 100 + __GNUC_MINOR__) > 800 + return __builtin_launder(reinterpret_cast(u)); + #else + return reinterpret_cast(u); + #endif + } + + // heap_string_factory + + template + class heap_string_factory + { + public: + using char_type = CharT; + using heap_string_type = heap_string; + private: + + using byte_allocator_type = typename std::allocator_traits::template rebind_alloc; + using byte_pointer = typename std::allocator_traits::pointer; + + using heap_string_allocator_type = typename std::allocator_traits::template rebind_alloc; + public: + using pointer = typename std::allocator_traits::pointer; + + struct storage_t + { + heap_string_type data; + char_type c[1]; + }; + typedef typename jsoncons_aligned_storage::type storage_type; + + static size_t aligned_size(std::size_t n) + { + return sizeof(storage_type) + n; + } + + public: + + static pointer create(const char_type* s, std::size_t length, Extra extra, const Allocator& alloc) + { + std::size_t len = aligned_size(length*sizeof(char_type)); + + std::size_t align = alignof(storage_type); + char* q = nullptr; + char* storage = nullptr; + byte_allocator_type byte_alloc(alloc); + uint8_t align_pad = 0; + + if (align <= 8) { + byte_pointer ptr = byte_alloc.allocate(len); + q = extension_traits::to_plain_pointer(ptr); + + if (reinterpret_cast(q) % align == 0) { + storage = q; + } else { + byte_alloc.deallocate(ptr, len); + } + } + + if (storage == nullptr) { + align_pad = uint8_t(align-1); + byte_pointer ptr = byte_alloc.allocate(align_pad+len); + q = extension_traits::to_plain_pointer(ptr); + storage = align_up(q, align); + JSONCONS_ASSERT(storage >= q); + } + + heap_string_type* ps = new(storage)heap_string_type(extra, byte_alloc); + + auto psa = launder_cast(storage); + + CharT* p = new(&psa->c)char_type[length + 1]; + std::memcpy(p, s, length*sizeof(char_type)); + p[length] = 0; + ps->p_ = std::pointer_traits::pointer_to(*p); + ps->length_ = length; + ps->offset_ = (uint8_t)(storage - q); + ps->align_pad_ = align_pad; + return std::pointer_traits::pointer_to(*ps); + } + + static void destroy(pointer ptr) + { + if (ptr != nullptr) + { + heap_string_type* rawp = extension_traits::to_plain_pointer(ptr); + + char* q = launder_cast(rawp); + + char* p = q - ptr->offset_; + + std::size_t mem_size = ptr->align_pad_ + aligned_size(ptr->length_*sizeof(char_type)); + byte_allocator_type byte_alloc(ptr->get_allocator()); + byte_alloc.deallocate(p,mem_size + ptr->offset_); + } + } + }; + +} // namespace detail +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/detail/optional.hpp b/third_party/jsoncons/detail/optional.hpp new file mode 100644 index 0000000000..bc21fcb638 --- /dev/null +++ b/third_party/jsoncons/detail/optional.hpp @@ -0,0 +1,487 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DETAIL_OPTIONAL_HPP +#define JSONCONS_DETAIL_OPTIONAL_HPP + +#include // placement new +#include +#include // std::swap +#include +#include + +namespace jsoncons +{ +namespace detail +{ + template + class optional; + + template + struct is_constructible_or_convertible_from_optional + : std::integral_constant< + bool, std::is_constructible&>::value || + std::is_constructible&&>::value || + std::is_constructible&>::value || + std::is_constructible&&>::value || + std::is_convertible&, T1>::value || + std::is_convertible&&, T1>::value || + std::is_convertible&, T1>::value || + std::is_convertible&&, T1>::value> {}; + + template + struct is_constructible_convertible_or_assignable_from_optional + : std::integral_constant< + bool, is_constructible_or_convertible_from_optional::value || + std::is_assignable&>::value || + std::is_assignable&&>::value || + std::is_assignable&>::value || + std::is_assignable&&>::value> {}; + + template + class optional + { + public: + using value_type = T; + private: + bool has_value_; + union { + char dummy_; + T value_; + }; + public: + constexpr optional() noexcept + : has_value_(false), dummy_{} + { + } + + // copy constructors + optional(const optional& other) + : has_value_(false), dummy_{} + { + if (other) + { + construct(*other); + } + } + + // converting + template ::value && + std::is_constructible::value && + std::is_convertible::value && + !is_constructible_or_convertible_from_optional::value && + std::is_copy_constructible::type>::value,int>::type = 0> + optional(const optional& other) + : has_value_(false), dummy_{} + { + if (other) + { + construct(*other); + } + } + + template ::value && + std::is_constructible::value && + !std::is_convertible::value && + !is_constructible_or_convertible_from_optional::value && + std::is_copy_constructible::type>::value,int>::type = 0> + explicit optional(const optional& other) + : has_value_(false), dummy_{} + { + if (other) + { + construct(*other); + } + } + + // move constructors + template + optional(optional&& other, + typename std::enable_if::type>::value>::type* = 0) + : has_value_(false), dummy_{} + { + if (other) + { + construct(std::move(other.value_)); + } + } + + // converting + template + optional(optional&& value, + typename std::enable_if::value && + std::is_constructible::value && + !is_constructible_or_convertible_from_optional::value && + std::is_convertible::value,int>::type = 0) // (8) + : has_value_(true), value_(std::forward(value)) + { + } + + template + explicit optional(optional&& value, + typename std::enable_if::value && + std::is_constructible::value && + !is_constructible_or_convertible_from_optional::value && + !std::is_convertible::value,int>::type = 0) // (8) + : has_value_(true), value_(std::forward(value)) + { + } + + + // value constructors + template + optional(T2&& value, + typename std::enable_if,typename std::decay::type>::value && + std::is_constructible::value && + std::is_convertible::value,int>::type = 0) // (8) + : has_value_(true), value_(std::forward(value)) + { + } + + template + explicit optional(T2&& value, + typename std::enable_if,typename std::decay::type>::value && + std::is_constructible::value && + !std::is_convertible::value,int>::type = 0) // (8) + : has_value_(true), value_(std::forward(value)) + { + } + + ~optional() noexcept + { + destroy(); + } + + optional& operator=(const optional& other) + { + if (other) + { + assign(*other); + } + else + { + reset(); + } + return *this; + } + + optional& operator=(optional&& other ) + { + if (other) + { + assign(std::move(*other)); + } + else + { + reset(); + } + return *this; + } + + template + typename std::enable_if, U>::value && + std::is_constructible::value && + !is_constructible_convertible_or_assignable_from_optional::value && + std::is_assignable::value, + optional&>::type + operator=(const optional& other) + { + if (other) + { + assign(*other); + } + else + { + destroy(); + } + return *this; + } + + template + typename std::enable_if, U>::value && + std::is_constructible::value && + !is_constructible_convertible_or_assignable_from_optional::value && + std::is_assignable::value, + optional&>::type + operator=(optional&& other) + { + if (other) + { + assign(std::move(*other)); + } + else + { + destroy(); + } + return *this; + } + + // value assignment + template + typename std::enable_if,typename std::decay::type>::value && + std::is_constructible::value && + std::is_assignable::value && + !(std::is_scalar::value && std::is_same::type>::value), + optional&>::type + operator=(T2&& v) + { + assign(std::forward(v)); + return *this; + } + + constexpr explicit operator bool() const noexcept + { + return has_value_; + } + constexpr bool has_value() const noexcept + { + return has_value_; + } + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4702) +#endif // _MSC_VER + + T& value() & + { + if (has_value_) + { + return get(); + } + JSONCONS_THROW(std::runtime_error("Bad optional access")); + } + + JSONCONS_CONSTEXPR const T& value() const & + { + if (has_value_) + { + return get(); + } + JSONCONS_THROW(std::runtime_error("Bad optional access")); + } + + template + constexpr T value_or(U&& default_value) const & + { + static_assert(std::is_copy_constructible::value, + "get_value_or: T must be copy constructible"); + static_assert(std::is_convertible::value, + "get_value_or: U must be convertible to T"); + return static_cast(*this) + ? **this + : static_cast(std::forward(default_value)); + } + + template + T value_or(U&& default_value) && + { + static_assert(std::is_move_constructible::value, + "get_value_or: T must be move constructible"); + static_assert(std::is_convertible::value, + "get_value_or: U must be convertible to T"); + return static_cast(*this) ? std::move(**this) + : static_cast(std::forward(default_value)); + } +#ifdef _MSC_VER +#pragma warning(pop) +#endif // _MSC_VER + + const T* operator->() const + { + return std::addressof(this->value_); + } + + T* operator->() + { + return std::addressof(this->value_); + } + + JSONCONS_CONSTEXPR const T& operator*() const& + { + return value(); + } + + T& operator*() & + { + return value(); + } + + void reset() noexcept + { + destroy(); + } + + void swap(optional& other) noexcept(std::is_nothrow_move_constructible::value /*&& + std::is_nothrow_swappable::value*/) + { + const bool contains_a_value = has_value(); + if (contains_a_value == other.has_value()) + { + if (contains_a_value) + { + using std::swap; + swap(**this, *other); + } + } + else + { + optional& source = contains_a_value ? *this : other; + optional& target = contains_a_value ? other : *this; + target = optional(*source); + source.reset(); + } + } + private: + constexpr const T& get() const { return this->value_; } + T& get() { return this->value_; } + + template + void construct(Args&&... args) + { + ::new (static_cast(&this->value_)) T(std::forward(args)...); + has_value_ = true; + } + + void destroy() noexcept + { + if (has_value_) + { + value_.~T(); + has_value_ = false; + } + } + + template + void assign(U&& u) + { + if (has_value_) + { + value_ = std::forward(u); + } + else + { + construct(std::forward(u)); + } + } + }; + + template + typename std::enable_if::value,void>::type + swap(optional& lhs, optional& rhs) noexcept + { + lhs.swap(rhs); + } + + template + constexpr bool operator==(const optional& lhs, const optional& rhs) noexcept + { + return lhs.has_value() == rhs.has_value() && (!lhs.has_value() || *lhs == *rhs); + } + + template + constexpr bool operator!=(const optional& lhs, const optional& rhs) noexcept + { + return lhs.has_value() != rhs.has_value() || (lhs.has_value() && *lhs != *rhs); + } + + template + constexpr bool operator<(const optional& lhs, const optional& rhs) noexcept + { + return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs); + } + + template + constexpr bool operator>(const optional& lhs, const optional& rhs) noexcept + { + return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs); + } + + template + constexpr bool operator<=(const optional& lhs, const optional& rhs) noexcept + { + return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs); + } + + template + constexpr bool operator>=(const optional& lhs, const optional& rhs) noexcept + { + return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs); + } + + template + constexpr bool operator==(const optional& lhs, const T2& rhs) noexcept + { + return lhs ? *lhs == rhs : false; + } + template + constexpr bool operator==(const T1& lhs, const optional& rhs) noexcept + { + return rhs ? lhs == *rhs : false; + } + + template + constexpr bool operator!=(const optional& lhs, const T2& rhs) noexcept + { + return lhs ? *lhs != rhs : true; + } + template + constexpr bool operator!=(const T1& lhs, const optional& rhs) noexcept + { + return rhs ? lhs != *rhs : true; + } + + template + constexpr bool operator<(const optional& lhs, const T2& rhs) noexcept + { + return lhs ? *lhs < rhs : true; + } + template + constexpr bool operator<(const T1& lhs, const optional& rhs) noexcept + { + return rhs ? lhs < *rhs : false; + } + + template + constexpr bool operator<=(const optional& lhs, const T2& rhs) noexcept + { + return lhs ? *lhs <= rhs : true; + } + template + constexpr bool operator<=(const T1& lhs, const optional& rhs) noexcept + { + return rhs ? lhs <= *rhs : false; + } + + template + constexpr bool operator>(const optional& lhs, const T2& rhs) noexcept + { + return lhs ? *lhs > rhs : false; + } + + template + constexpr bool operator>(const T1& lhs, const optional& rhs) noexcept + { + return rhs ? lhs > *rhs : true; + } + + template + constexpr bool operator>=(const optional& lhs, const T2& rhs) noexcept + { + return lhs ? *lhs >= rhs : false; + } + template + constexpr bool operator>=(const T1& lhs, const optional& rhs) noexcept + { + return rhs ? lhs >= *rhs : true; + } + +} // namespace detail +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/detail/parse_number.hpp b/third_party/jsoncons/detail/parse_number.hpp new file mode 100644 index 0000000000..bd11143046 --- /dev/null +++ b/third_party/jsoncons/detail/parse_number.hpp @@ -0,0 +1,1047 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DETAIL_PARSE_NUMBER_HPP +#define JSONCONS_DETAIL_PARSE_NUMBER_HPP + +#include +#include +#include +#include +#include +#include +#include // std::numeric_limits +#include // std::enable_if +#include +#include +#include +#include + +namespace jsoncons { namespace detail { + + enum class to_integer_errc : uint8_t {success=0, overflow, invalid_digit, invalid_number}; + + class to_integer_error_category_impl + : public std::error_category + { + public: + const char* name() const noexcept override + { + return "jsoncons/to_integer_unchecked"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case to_integer_errc::overflow: + return "Integer overflow"; + case to_integer_errc::invalid_digit: + return "Invalid digit"; + case to_integer_errc::invalid_number: + return "Invalid number"; + default: + return "Unknown to_integer_unchecked error"; + } + } + }; + + inline + const std::error_category& to_integer_error_category() + { + static to_integer_error_category_impl instance; + return instance; + } + + inline + std::error_code make_error_code(to_integer_errc e) + { + return std::error_code(static_cast(e),to_integer_error_category()); + } + +} // namespace detail +} // namespace jsoncons + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +namespace jsoncons { namespace detail { + +template +struct to_integer_result +{ + const CharT* ptr; + to_integer_errc ec; + constexpr to_integer_result(const CharT* ptr_) + : ptr(ptr_), ec(to_integer_errc()) + { + } + constexpr to_integer_result(const CharT* ptr_, to_integer_errc ec_) + : ptr(ptr_), ec(ec_) + { + } + + to_integer_result(const to_integer_result&) = default; + + to_integer_result& operator=(const to_integer_result&) = default; + + constexpr explicit operator bool() const noexcept + { + return ec == to_integer_errc(); + } + std::error_code error_code() const + { + return make_error_code(ec); + } +}; + +enum class integer_chars_format : uint8_t {decimal=1,hex}; +enum class integer_chars_state {initial,minus,integer,binary,octal,decimal,base16}; + +template +bool is_base10(const CharT* s, std::size_t length) +{ + integer_chars_state state = integer_chars_state::initial; + + const CharT* end = s + length; + for (;s < end; ++s) + { + switch(state) + { + case integer_chars_state::initial: + { + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + state = integer_chars_state::decimal; + break; + case '-': + state = integer_chars_state::minus; + break; + default: + return false; + } + break; + } + case integer_chars_state::minus: + { + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + state = integer_chars_state::decimal; + break; + default: + return false; + } + break; + } + case integer_chars_state::decimal: + { + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + break; + default: + return false; + } + break; + } + default: + break; + } + } + return state == integer_chars_state::decimal ? true : false; +} + +template +bool is_base16(const CharT* s, std::size_t length) +{ + integer_chars_state state = integer_chars_state::initial; + + const CharT* end = s + length; + for (;s < end; ++s) + { + switch(state) + { + case integer_chars_state::initial: + { + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': // Must be base16 + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + state = integer_chars_state::base16; + break; + default: + return false; + } + break; + } + case integer_chars_state::base16: + { + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': // Must be base16 + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + state = integer_chars_state::base16; + break; + default: + return false; + } + break; + } + default: + break; + } + } + return state == integer_chars_state::base16 ? true : false; +} + +template +typename std::enable_if::is_specialized && !extension_traits::integer_limits::is_signed,to_integer_result>::type +decimal_to_integer(const CharT* s, std::size_t length, T& n) +{ + n = 0; + + integer_chars_state state = integer_chars_state::initial; + + const CharT* end = s + length; + while (s < end) + { + switch(state) + { + case integer_chars_state::initial: + { + switch(*s) + { + case '0': + if (++s == end) + { + return (++s == end) ? to_integer_result(s) : to_integer_result(s, to_integer_errc()); + } + else + { + return to_integer_result(s, to_integer_errc::invalid_digit); + } + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': // Must be decimal + state = integer_chars_state::decimal; + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + break; + } + case integer_chars_state::decimal: + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_10 = max_value / 10; + for (; s < end; ++s) + { + T x = 0; + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + x = static_cast(*s) - static_cast('0'); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n > max_value_div_10) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 10; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n += x; + } + break; + } + default: + JSONCONS_UNREACHABLE(); + break; + } + } + return (state == integer_chars_state::initial) ? to_integer_result(s, to_integer_errc::invalid_number) : to_integer_result(s, to_integer_errc()); +} + +template +typename std::enable_if::is_specialized && extension_traits::integer_limits::is_signed,to_integer_result>::type +decimal_to_integer(const CharT* s, std::size_t length, T& n) +{ + n = 0; + + if (length == 0) + { + return to_integer_result(s, to_integer_errc::invalid_number); + } + + bool is_negative = *s == '-' ? true : false; + if (is_negative) + { + ++s; + --length; + } + + using U = typename extension_traits::make_unsigned::type; + + U u; + auto ru = decimal_to_integer(s, length, u); + if (ru.ec != to_integer_errc()) + { + return to_integer_result(ru.ptr, ru.ec); + } + if (is_negative) + { + if (u > static_cast(-((extension_traits::integer_limits::lowest)()+T(1))) + U(1)) + { + return to_integer_result(ru.ptr, to_integer_errc::overflow); + } + else + { + n = static_cast(U(0) - u); + return to_integer_result(ru.ptr, to_integer_errc()); + } + } + else + { + if (u > static_cast((extension_traits::integer_limits::max)())) + { + return to_integer_result(ru.ptr, to_integer_errc::overflow); + } + else + { + n = static_cast(u); + return to_integer_result(ru.ptr, to_integer_errc()); + } + } +} + +template +typename std::enable_if::is_specialized && !extension_traits::integer_limits::is_signed,to_integer_result>::type +to_integer(const CharT* s, std::size_t length, T& n) +{ + n = 0; + + integer_chars_state state = integer_chars_state::initial; + + const CharT* end = s + length; + while (s < end) + { + switch(state) + { + case integer_chars_state::initial: + { + switch(*s) + { + case '0': + state = integer_chars_state::integer; // Could be binary, octal, hex + ++s; + break; + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': // Must be decimal + state = integer_chars_state::decimal; + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + break; + } + case integer_chars_state::integer: + { + switch(*s) + { + case 'b':case 'B': + { + state = integer_chars_state::binary; + ++s; + break; + } + case 'x':case 'X': + { + state = integer_chars_state::base16; + ++s; + break; + } + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + { + state = integer_chars_state::octal; + break; + } + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + break; + } + case integer_chars_state::binary: + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_2 = max_value / 2; + for (; s < end; ++s) + { + T x = 0; + switch(*s) + { + case '0':case '1': + x = static_cast(*s) - static_cast('0'); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n > max_value_div_2) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 2; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n += x; + } + break; + } + case integer_chars_state::octal: + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_8 = max_value / 8; + for (; s < end; ++s) + { + T x = 0; + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7': + x = static_cast(*s) - static_cast('0'); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n > max_value_div_8) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 8; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n += x; + } + break; + } + case integer_chars_state::decimal: + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_10 = max_value / 10; + for (; s < end; ++s) + { + T x = 0; + switch(*s) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + x = static_cast(*s) - static_cast('0'); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n > max_value_div_10) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 10; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n += x; + } + break; + } + case integer_chars_state::base16: + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_16 = max_value / 16; + for (; s < end; ++s) + { + CharT c = *s; + T x = 0; + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + x = c - '0'; + break; + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + x = c - ('a' - 10); + break; + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + x = c - ('A' - 10); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n > max_value_div_16) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 16; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + + n += x; + } + break; + } + default: + JSONCONS_UNREACHABLE(); + break; + } + } + return (state == integer_chars_state::initial) ? to_integer_result(s, to_integer_errc::invalid_number) : to_integer_result(s, to_integer_errc()); +} + +template +typename std::enable_if::is_specialized && extension_traits::integer_limits::is_signed,to_integer_result>::type +to_integer(const CharT* s, std::size_t length, T& n) +{ + n = 0; + + if (length == 0) + { + return to_integer_result(s, to_integer_errc::invalid_number); + } + + bool is_negative = *s == '-' ? true : false; + if (is_negative) + { + ++s; + --length; + } + + using U = typename extension_traits::make_unsigned::type; + + U u; + auto ru = to_integer(s, length, u); + if (ru.ec != to_integer_errc()) + { + return to_integer_result(ru.ptr, ru.ec); + } + if (is_negative) + { + if (u > static_cast(-((extension_traits::integer_limits::lowest)()+T(1))) + U(1)) + { + return to_integer_result(ru.ptr, to_integer_errc::overflow); + } + else + { + n = static_cast(U(0) - u); + return to_integer_result(ru.ptr, to_integer_errc()); + } + } + else + { + if (u > static_cast((extension_traits::integer_limits::max)())) + { + return to_integer_result(ru.ptr, to_integer_errc::overflow); + } + else + { + n = static_cast(u); + return to_integer_result(ru.ptr, to_integer_errc()); + } + } +} + +template +typename std::enable_if::is_specialized,to_integer_result>::type +to_integer(const CharT* s, T& n) +{ + return to_integer(s, std::char_traits::length(s), n); +} + +// Precondition: s satisfies + +// digit +// digit1-digits +// - digit +// - digit1-digits + +template +typename std::enable_if::is_specialized && !extension_traits::integer_limits::is_signed,to_integer_result>::type +to_integer_unchecked(const CharT* s, std::size_t length, T& n) +{ + static_assert(extension_traits::integer_limits::is_specialized, "Integer type not specialized"); + JSONCONS_ASSERT(length > 0); + + n = 0; + const CharT* end = s + length; + if (*s == '-') + { + static constexpr T min_value = (extension_traits::integer_limits::lowest)(); + static constexpr T min_value_div_10 = min_value / 10; + ++s; + for (; s < end; ++s) + { + T x = (T)*s - (T)('0'); + if (n < min_value_div_10) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 10; + if (n < min_value + x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + + n -= x; + } + } + else + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_10 = max_value / 10; + for (; s < end; ++s) + { + T x = static_cast(*s) - static_cast('0'); + if (n > max_value_div_10) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 10; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + + n += x; + } + } + + return to_integer_result(s, to_integer_errc()); +} + +// Precondition: s satisfies + +// digit +// digit1-digits +// - digit +// - digit1-digits + +template +typename std::enable_if::is_specialized && extension_traits::integer_limits::is_signed,to_integer_result>::type +to_integer_unchecked(const CharT* s, std::size_t length, T& n) +{ + static_assert(extension_traits::integer_limits::is_specialized, "Integer type not specialized"); + JSONCONS_ASSERT(length > 0); + + n = 0; + + const CharT* end = s + length; + if (*s == '-') + { + static constexpr T min_value = (extension_traits::integer_limits::lowest)(); + static constexpr T min_value_div_10 = min_value / 10; + ++s; + for (; s < end; ++s) + { + T x = (T)*s - (T)('0'); + if (n < min_value_div_10) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 10; + if (n < min_value + x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + + n -= x; + } + } + else + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_10 = max_value / 10; + for (; s < end; ++s) + { + T x = static_cast(*s) - static_cast('0'); + if (n > max_value_div_10) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 10; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + + n += x; + } + } + + return to_integer_result(s, to_integer_errc()); +} + +// hex_to_integer + +template +typename std::enable_if::is_specialized && extension_traits::integer_limits::is_signed,to_integer_result>::type +hex_to_integer(const CharT* s, std::size_t length, T& n) +{ + static_assert(extension_traits::integer_limits::is_specialized, "Integer type not specialized"); + JSONCONS_ASSERT(length > 0); + + n = 0; + + const CharT* end = s + length; + if (*s == '-') + { + static constexpr T min_value = (extension_traits::integer_limits::lowest)(); + static constexpr T min_value_div_16 = min_value / 16; + ++s; + for (; s < end; ++s) + { + CharT c = *s; + T x = 0; + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + x = c - '0'; + break; + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + x = c - ('a' - 10); + break; + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + x = c - ('A' - 10); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n < min_value_div_16) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 16; + if (n < min_value + x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n -= x; + } + } + else + { + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_16 = max_value / 16; + for (; s < end; ++s) + { + CharT c = *s; + T x = 0; + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + x = c - '0'; + break; + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + x = c - ('a' - 10); + break; + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + x = c - ('A' - 10); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n > max_value_div_16) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 16; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + + n += x; + } + } + + return to_integer_result(s, to_integer_errc()); +} + +template +typename std::enable_if::is_specialized && !extension_traits::integer_limits::is_signed,to_integer_result>::type +hex_to_integer(const CharT* s, std::size_t length, T& n) +{ + static_assert(extension_traits::integer_limits::is_specialized, "Integer type not specialized"); + JSONCONS_ASSERT(length > 0); + + n = 0; + const CharT* end = s + length; + + static constexpr T max_value = (extension_traits::integer_limits::max)(); + static constexpr T max_value_div_16 = max_value / 16; + for (; s < end; ++s) + { + CharT c = *s; + T x = *s; + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + x = c - '0'; + break; + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + x = c - ('a' - 10); + break; + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + x = c - ('A' - 10); + break; + default: + return to_integer_result(s, to_integer_errc::invalid_digit); + } + if (n > max_value_div_16) + { + return to_integer_result(s, to_integer_errc::overflow); + } + n = n * 16; + if (n > max_value - x) + { + return to_integer_result(s, to_integer_errc::overflow); + } + + n += x; + } + + return to_integer_result(s, to_integer_errc()); +} + + +#if defined(JSONCONS_HAS_STD_FROM_CHARS) && JSONCONS_HAS_STD_FROM_CHARS + +class chars_to +{ +public: + + char get_decimal_point() const + { + return '.'; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t len) const + { + double val = 0; + const auto res = std::from_chars(s, s+len, val); + if (res.ec != std::errc()) + { + JSONCONS_THROW(json_runtime_error("Convert chars to double failed")); + } + return val; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t len) const + { + std::string input(len,'0'); + for (size_t i = 0; i < len; ++i) + { + input[i] = static_cast(s[i]); + } + + double val = 0; + const auto res = std::from_chars(input.data(), input.data() + len, val); + if (res.ec != std::errc()) + { + JSONCONS_THROW(json_runtime_error("Convert chars to double failed")); + } + return val; + } +}; +#elif defined(JSONCONS_HAS_MSC_STRTOD_L) + +class chars_to +{ +private: + _locale_t locale_; +public: + chars_to() + { + locale_ = _create_locale(LC_NUMERIC, "C"); + } + ~chars_to() noexcept + { + _free_locale(locale_); + } + + chars_to(const chars_to&) + { + locale_ = _create_locale(LC_NUMERIC, "C"); + } + + chars_to& operator=(const chars_to&) + { + // Don't assign locale + return *this; + } + + char get_decimal_point() const + { + return '.'; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t) const + { + CharT *end = nullptr; + double val = _strtod_l(s, &end, locale_); + if (s == end) + { + JSONCONS_THROW(json_runtime_error("Convert string to double failed")); + } + return val; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t) const + { + CharT *end = nullptr; + double val = _wcstod_l(s, &end, locale_); + if (s == end) + { + JSONCONS_THROW(json_runtime_error("Convert string to double failed")); + } + return val; + } +}; + +#elif defined(JSONCONS_HAS_STRTOLD_L) + +class chars_to +{ +private: + locale_t locale_; +public: + chars_to() + { + locale_ = newlocale(LC_ALL_MASK, "C", (locale_t) 0); + } + ~chars_to() noexcept + { + freelocale(locale_); + } + + chars_to(const chars_to&) + { + locale_ = newlocale(LC_ALL_MASK, "C", (locale_t) 0); + } + + chars_to& operator=(const chars_to&) + { + return *this; + } + + char get_decimal_point() const + { + return '.'; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t) const + { + char *end = nullptr; + double val = strtold_l(s, &end, locale_); + if (s == end) + { + JSONCONS_THROW(json_runtime_error("Convert string to double failed")); + } + return val; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t) const + { + CharT *end = nullptr; + double val = wcstold_l(s, &end, locale_); + if (s == end) + { + JSONCONS_THROW(json_runtime_error("Convert string to double failed")); + } + return val; + } +}; + +#else +class chars_to +{ +private: + std::vector buffer_; + char decimal_point_; +public: + chars_to() + : buffer_() + { + struct lconv * lc = localeconv(); + if (lc != nullptr && lc->decimal_point[0] != 0) + { + decimal_point_ = lc->decimal_point[0]; + } + else + { + decimal_point_ = '.'; + } + buffer_.reserve(100); + } + + chars_to(const chars_to&) = default; + chars_to& operator=(const chars_to&) = default; + + char get_decimal_point() const + { + return decimal_point_; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t /*length*/) const + { + CharT *end = nullptr; + double val = strtod(s, &end); + if (s == end) + { + JSONCONS_THROW(json_runtime_error("Convert string to double failed")); + } + return val; + } + + template + typename std::enable_if::value,double>::type + operator()(const CharT* s, std::size_t /*length*/) const + { + CharT *end = nullptr; + double val = wcstod(s, &end); + if (s == end) + { + JSONCONS_THROW(json_runtime_error("Convert string to double failed")); + } + return val; + } +}; +#endif + +}} + +#endif diff --git a/third_party/jsoncons/detail/span.hpp b/third_party/jsoncons/detail/span.hpp new file mode 100644 index 0000000000..b17a8436e9 --- /dev/null +++ b/third_party/jsoncons/detail/span.hpp @@ -0,0 +1,188 @@ +#ifndef JSONCONS_DETAIL_SPAN_HPP +#define JSONCONS_DETAIL_SPAN_HPP + +#include // std::swap +#include // std::addressof +#include // std::enable_if, std::true_type, std::false_type +#include +#include +#include +#include + +namespace jsoncons +{ +namespace detail +{ + constexpr std::size_t dynamic_extent = (std::numeric_limits::max)(); + + template< typename T, std::size_t Extent = dynamic_extent> + class span; + + template + struct is_span : std::false_type{}; + + template< typename T> + struct is_span> : std::true_type{}; + + template< + typename T, + std::size_t Extent + > class span + { + public: + using element_type = T; + using value_type = typename std::remove_const::type; + using size_type = std::size_t; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using const_pointer = const T*; + using reference = T&; + using const_reference = const T&; + using iterator = pointer; + using const_iterator = const_pointer; + using reverse_iterator = std::reverse_iterator; + using const_reverse_iterator = std::reverse_iterator; + private: + pointer data_; + size_type size_; + public: + static constexpr std::size_t extent = Extent; + + constexpr span() noexcept + : data_(nullptr), size_(0) + { + } + constexpr span(pointer data, size_type size) + : data_(data), size_(size) + { + } + + template + constexpr span(C& c, + typename std::enable_if::value && !extension_traits::is_std_array::value && extension_traits::is_compatible_element::value && extension_traits::has_data_and_size::value>::type* = 0) + : data_(c.data()), size_(c.size()) + { + } + + template + span(element_type (&arr)[N]) noexcept + : data_(std::addressof(arr[0])), size_(N) + { + } + + template + span(std::array& arr, + typename std::enable_if<(extent == dynamic_extent || extent == N)>::type* = 0) noexcept + : data_(arr.data()), size_(arr.size()) + { + } + + template + span(const std::array& arr, + typename std::enable_if<(extent == dynamic_extent || extent == N)>::type* = 0) noexcept + : data_(arr.data()), size_(arr.size()) + { + } + + template + constexpr span(const C& c, + typename std::enable_if::value && !extension_traits::is_std_array::value && extension_traits::is_compatible_element::value && extension_traits::has_data_and_size::value>::type* = 0) + : data_(c.data()), size_(c.size()) + { + } + + template + constexpr span(const span& s, + typename std::enable_if<(N == dynamic_extent || N == extent) && std::is_convertible::value>::type* = 0) noexcept + : data_(s.data()), size_(s.size()) + { + } + + constexpr span(const span& other) noexcept = default; + + span& operator=( const span& other ) noexcept = default; + + constexpr pointer data() const noexcept + { + return data_; + } + + constexpr size_type size() const noexcept + { + return size_; + } + + constexpr bool empty() const noexcept + { + return size_ == 0; + } + + constexpr reference operator[](size_type index) const + { + return data_[index]; + } + + // iterator support + const_iterator begin() const noexcept + { + return data_; + } + const_iterator end() const noexcept + { + return data_ + size_; + } + const_iterator cbegin() const noexcept + { + return data_; + } + const_iterator cend() const noexcept + { + return data_ + size_; + } + const_reverse_iterator rbegin() const noexcept + { + return const_reverse_iterator(end()); + } + const_reverse_iterator rend() const noexcept + { + return const_reverse_iterator(begin()); + } + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(end()); + } + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(begin()); + } + + span + first(std::size_t count) const + { + JSONCONS_ASSERT(count <= size()); + + return span< element_type, dynamic_extent >( data(), count ); + } + + span + last(std::size_t count) const + { + JSONCONS_ASSERT(count <= size()); + + return span(data() + ( size() - count ), count); + } + + span + subspan(std::size_t offset, std::size_t count = dynamic_extent) const + { + //JSONCONS_ASSERT((offset <= size() && (count == dynamic_extent || (offset + count <= size())))); + + return span( + data() + offset, count == dynamic_extent ? size() - offset : count ); + } + }; + +} // namespace detail +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/detail/string_view.hpp b/third_party/jsoncons/detail/string_view.hpp new file mode 100644 index 0000000000..44552d35d4 --- /dev/null +++ b/third_party/jsoncons/detail/string_view.hpp @@ -0,0 +1,559 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_STRING_VIEW_HPP +#define JSONCONS_STRING_VIEW_HPP + +#include +#include +#include +#include +#include +#include // std::find, std::min, std::reverse +#include +#include +#include +#include +#include // std::basic_istream +#include + +namespace jsoncons { +namespace detail { + + template > + class basic_string_view + { + private: + const CharT* data_; + std::size_t length_; + public: + using value_type = CharT; + using const_reference = const CharT&; + using traits_type = Traits; + using size_type = std::size_t; + static constexpr size_type npos = size_type(-1); + using const_iterator = const CharT*; + using iterator = const CharT*; + using const_reverse_iterator = std::reverse_iterator; + + constexpr basic_string_view() noexcept + : data_(nullptr), length_(0) + { + } + constexpr basic_string_view(const CharT* data, std::size_t length) + : data_(data), length_(length) + { + } + + basic_string_view(const CharT* data) + : data_(data), length_(Traits::length(data)) + { + } + constexpr basic_string_view(const basic_string_view& other) noexcept = default; + + template + JSONCONS_CPP14_CONSTEXPR basic_string_view(const std::basic_string& s) noexcept + : data_(s.data()), length_(s.length()) + { + } + + JSONCONS_CPP14_CONSTEXPR basic_string_view& operator=( const basic_string_view& view ) noexcept + { + data_ = view.data(); + length_ = view.length(); + + return *this; + } + + template + explicit operator std::basic_string() const + { + return std::basic_string(data_,length_); + } + + // iterator support + const_iterator begin() const noexcept + { + return data_; + } + const_iterator end() const noexcept + { + return data_ + length_; + } + const_iterator cbegin() const noexcept + { + return data_; + } + const_iterator cend() const noexcept + { + return data_ + length_; + } + const_reverse_iterator rbegin() const noexcept + { + return const_reverse_iterator(end()); + } + const_reverse_iterator rend() const noexcept + { + return const_reverse_iterator(begin()); + } + const_reverse_iterator crbegin() const noexcept + { + return const_reverse_iterator(end()); + } + const_reverse_iterator crend() const noexcept + { + return const_reverse_iterator(begin()); + } + + // capacity + + std::size_t size() const + { + return length_; + } + + std::size_t length() const + { + return length_; + } + size_type max_size() const noexcept + { + return length_; + } + bool empty() const noexcept + { + return length_ == 0; + } + + // element access + + const_reference operator[](size_type pos) const + { + return data_[pos]; + } + + const_reference at(std::size_t pos) const + { + if (pos >= length_) + { + JSONCONS_THROW(std::out_of_range("pos exceeds length")); + } + return data_[pos]; + } + + const_reference front() const + { + return data_[0]; + } + const_reference back() const + { + return data_[length_-1]; + } + + const CharT* data() const + { + return data_; + } + + // string operations + + basic_string_view substr(size_type pos, size_type n=npos) const + { + if (pos > length_) + { + JSONCONS_THROW(std::out_of_range("pos exceeds size")); + } + if (n == npos || pos + n > length_) + { + n = length_ - pos; + } + return basic_string_view(data_ + pos, n); + } + + int compare(const basic_string_view& s) const noexcept + { + const int rc = Traits::compare(data_, s.data_, (std::min)(length_, s.length_)); + return rc != 0 ? rc : (length_ == s.length_ ? 0 : length_ < s.length_ ? -1 : 1); + } + + int compare(const CharT* data) const noexcept + { + const size_t length = Traits::length(data); + const int rc = Traits::compare(data_, data, (std::min)(length_, length)); + return rc != 0 ? rc : (length_ == length? 0 : length_ < length? -1 : 1); + } + + template + int compare(const std::basic_string& s) const noexcept + { + const int rc = Traits::compare(data_, s.data(), (std::min)(length_, s.length())); + return rc != 0 ? rc : (length_ == s.length() ? 0 : length_ < s.length() ? -1 : 1); + } + + size_type find(basic_string_view s, size_type pos = 0) const noexcept + { + if (pos > length_) + { + return npos; + } + if (s.length_ == 0) + { + return pos; + } + const_iterator it = std::search(cbegin() + pos, cend(), + s.cbegin(), s.cend(), Traits::eq); + return it == cend() ? npos : std::distance(cbegin(), it); + } + size_type find(CharT ch, size_type pos = 0) const noexcept + { + return find(basic_string_view(&ch, 1), pos); + } + size_type find(const CharT* s, size_type pos, size_type n) const noexcept + { + return find(basic_string_view(s, n), pos); + } + size_type find(const CharT* s, size_type pos = 0) const noexcept + { + return find(basic_string_view(s), pos); + } + + size_type rfind(basic_string_view s, size_type pos = npos) const noexcept + { + if (length_ < s.length_) + { + return npos; + } + if (pos > length_ - s.length_) + { + pos = length_ - s.length_; + } + if (s.length_ == 0) + { + return pos; + } + for (const CharT* p = data_ + pos; true; --p) + { + if (Traits::compare(p, s.data_, s.length_) == 0) + { + return p - data_; + } + if (p == data_) + { + return npos; + } + }; + } + size_type rfind(CharT ch, size_type pos = npos) const noexcept + { + return rfind(basic_string_view(&ch, 1), pos); + } + size_type rfind(const CharT* s, size_type pos, size_type n) const noexcept + { + return rfind(basic_string_view(s, n), pos); + } + size_type rfind(const CharT* s, size_type pos = npos) const noexcept + { + return rfind(basic_string_view(s), pos); + } + + size_type find_first_of(basic_string_view s, size_type pos = 0) const noexcept + { + if (pos >= length_ || s.length_ == 0) + { + return npos; + } + const_iterator it = std::find_first_of + (cbegin() + pos, cend(), s.cbegin(), s.cend(), Traits::eq); + return it == cend() ? npos : std::distance (cbegin(), it); + } + size_type find_first_of(CharT ch, size_type pos = 0) const noexcept + { + return find_first_of(basic_string_view(&ch, 1), pos); + } + size_type find_first_of(const CharT* s, size_type pos, size_type n) const noexcept + { + return find_first_of(basic_string_view(s, n), pos); + } + size_type find_first_of(const CharT* s, size_type pos = 0) const noexcept + { + return find_first_of(basic_string_view(s), pos); + } + + size_type find_last_of(basic_string_view s, size_type pos = npos) const noexcept + { + if (s.length_ == 0) + { + return npos; + } + if (pos >= length_) + { + pos = 0; + } + else + { + pos = length_ - (pos+1); + } + const_reverse_iterator it = std::find_first_of + (crbegin() + pos, crend(), s.cbegin(), s.cend(), Traits::eq); + return it == crend() ? npos : (length_ - 1 - std::distance(crbegin(), it)); + } + size_type find_last_of(CharT ch, size_type pos = npos) const noexcept + { + return find_last_of(basic_string_view(&ch, 1), pos); + } + size_type find_last_of(const CharT* s, size_type pos, size_type n) const noexcept + { + return find_last_of(basic_string_view(s, n), pos); + } + size_type find_last_of(const CharT* s, size_type pos = npos) const noexcept + { + return find_last_of(basic_string_view(s), pos); + } + + size_type find_first_not_of(basic_string_view s, size_type pos = 0) const noexcept + { + if (pos >= length_) + return npos; + if (s.length_ == 0) + return pos; + + const_iterator it = cend(); + for (auto p = cbegin()+pos; p != cend(); ++p) + { + if (Traits::find(s.data_, s.length_, *p) == 0) + { + it = p; + break; + } + } + return it == cend() ? npos : std::distance (cbegin(), it); + } + size_type find_first_not_of(CharT ch, size_type pos = 0) const noexcept + { + return find_first_not_of(basic_string_view(&ch, 1), pos); + } + size_type find_first_not_of(const CharT* s, size_type pos, size_type n) const noexcept + { + return find_first_not_of(basic_string_view(s, n), pos); + } + size_type find_first_not_of(const CharT* s, size_type pos = 0) const noexcept + { + return find_first_not_of(basic_string_view(s), pos); + } + + size_type find_last_not_of(basic_string_view s, size_type pos = npos) const noexcept + { + if (pos >= length_) + { + pos = length_ - 1; + } + if (s.length_ == 0) + { + return pos; + } + pos = length_ - (pos+1); + + const_iterator it = crend(); + for (auto p = crbegin()+pos; p != crend(); ++p) + { + if (Traits::find(s.data_, s.length_, *p) == 0) + { + it = p; + break; + } + } + return it == crend() ? npos : (length_ - 1 - std::distance(crbegin(), it)); + } + size_type find_last_not_of(CharT ch, size_type pos = npos) const noexcept + { + return find_last_not_of(basic_string_view(&ch, 1), pos); + } + size_type find_last_not_of(const CharT* s, size_type pos, size_type n) const noexcept + { + return find_last_not_of(basic_string_view(s, n), pos); + } + size_type find_last_not_of(const CharT* s, size_type pos = npos) const noexcept + { + return find_last_not_of(basic_string_view(s), pos); + } + + friend std::basic_ostream& operator<<(std::basic_ostream& os, const basic_string_view& sv) + { + os.write(sv.data_,sv.length_); + return os; + } + }; + + // == + template + bool operator==(const basic_string_view& lhs, + const basic_string_view& rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + template + bool operator==(const basic_string_view& lhs, + const std::basic_string& rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + template + bool operator==(const std::basic_string& lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) == 0; + } + template + bool operator==(const basic_string_view& lhs, + const CharT* rhs) noexcept + { + return lhs.compare(rhs) == 0; + } + template + bool operator==(const CharT* lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) == 0; + } + + // != + template + bool operator!=(const basic_string_view& lhs, + const basic_string_view& rhs) noexcept + { + return lhs.compare(rhs) != 0; + } + template + bool operator!=(const basic_string_view& lhs, + const std::basic_string& rhs) noexcept + { + return lhs.compare(rhs) != 0; + } + template + bool operator!=(const std::basic_string& lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) != 0; + } + template + bool operator!=(const basic_string_view& lhs, + const CharT* rhs) noexcept + { + return lhs.compare(rhs) != 0; + } + template + bool operator!=(const CharT* lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) != 0; + } + + // <= + template + bool operator<=(const basic_string_view& lhs, + const basic_string_view& rhs) noexcept + { + return lhs.compare(rhs) <= 0; + } + template + bool operator<=(const basic_string_view& lhs, + const std::basic_string& rhs) noexcept + { + return lhs.compare(rhs) <= 0; + } + template + bool operator<=(const std::basic_string& lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) >= 0; + } + + // < + template + bool operator<(const basic_string_view& lhs, + const basic_string_view& rhs) noexcept + { + return lhs.compare(rhs) < 0; + } + template + bool operator<(const basic_string_view& lhs, + const std::basic_string& rhs) noexcept + { + return lhs.compare(rhs) < 0; + } + template + bool operator<(const std::basic_string& lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) > 0; + } + + // >= + template + bool operator>=(const basic_string_view& lhs, + const basic_string_view& rhs) noexcept + { + return lhs.compare(rhs) >= 0; + } + template + bool operator>=(const basic_string_view& lhs, + const std::basic_string& rhs) noexcept + { + return lhs.compare(rhs) >= 0; + } + template + bool operator>=(const std::basic_string& lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) <= 0; + } + + // > + template + bool operator>(const basic_string_view& lhs, + const basic_string_view& rhs) noexcept + { + return lhs.compare(rhs) > 0; + } + template + bool operator>(const basic_string_view& lhs, + const std::basic_string& rhs) noexcept + { + return lhs.compare(rhs) > 0; + } + template + bool operator>(const std::basic_string& lhs, + const basic_string_view& rhs) noexcept + { + return rhs.compare(lhs) < 0; + } + + using string_view = basic_string_view; + using wstring_view = basic_string_view; + +} // namespace detail +} // namespace jsoncons + +namespace std { + template + struct hash> + { + size_t operator()(const jsoncons::detail::basic_string_view& s) const noexcept + { + const int p = 53; + const int m = 1000000009; + size_t hash_value = 0; + size_t p_pow = 1; + for (CharT c : s) { + hash_value = (hash_value + (c - 'a' + 1) * p_pow) % m; + p_pow = (p_pow * p) % m; + } + return hash_value; + } + }; +} // namespace std + +#endif diff --git a/third_party/jsoncons/detail/write_number.hpp b/third_party/jsoncons/detail/write_number.hpp new file mode 100644 index 0000000000..328214343a --- /dev/null +++ b/third_party/jsoncons/detail/write_number.hpp @@ -0,0 +1,566 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_DETAIL_WRITE_NUMBER_HPP +#define JSONCONS_DETAIL_WRITE_NUMBER_HPP + +#include +#include +#include +#include +#include // std::numeric_limits +#include +#include // snprintf +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace detail { + + inline + char to_hex_character(uint8_t c) + { + return (char)((c < 10) ? ('0' + c) : ('A' - 10 + c)); + } + + // from_integer + + template + typename std::enable_if::value,std::size_t>::type + from_integer(Integer value, Result& result) + { + using char_type = typename Result::value_type; + + char_type buf[255]; + char_type *p = buf; + const char_type* last = buf+255; + + bool is_negative = value < 0; + + if (value < 0) + { + do + { + *p++ = static_cast(48 - (value % 10)); + } + while ((value /= 10) && (p < last)); + } + else + { + + do + { + *p++ = static_cast(48 + value % 10); + } + while ((value /= 10) && (p < last)); + } + JSONCONS_ASSERT(p != last); + + std::size_t count = (p - buf); + if (is_negative) + { + result.push_back('-'); + ++count; + } + while (--p >= buf) + { + result.push_back(*p); + } + + return count; + } + + // integer_to_string_hex + + template + typename std::enable_if::value,std::size_t>::type + integer_to_string_hex(Integer value, Result& result) + { + using char_type = typename Result::value_type; + + char_type buf[255]; + char_type *p = buf; + const char_type* last = buf+255; + + bool is_negative = value < 0; + + if (value < 0) + { + do + { + *p++ = to_hex_character(0-(value % 16)); + } + while ((value /= 16) && (p < last)); + } + else + { + + do + { + *p++ = to_hex_character(value % 16); + } + while ((value /= 16) && (p < last)); + } + JSONCONS_ASSERT(p != last); + + std::size_t count = (p - buf); + if (is_negative) + { + result.push_back('-'); + ++count; + } + while (--p >= buf) + { + result.push_back(*p); + } + + return count; + } + + // write_double + + // fast exponent + template + void fill_exponent(int K, Result& result) + { + if (K < 0) + { + result.push_back('-'); + K = -K; + } + else + { + result.push_back('+'); // compatibility with sprintf + } + + if (K < 10) + { + result.push_back('0'); // compatibility with sprintf + result.push_back((char)('0' + K)); + } + else if (K < 100) + { + result.push_back((char)('0' + K / 10)); K %= 10; + result.push_back((char)('0' + K)); + } + else if (K < 1000) + { + result.push_back((char)('0' + K / 100)); K %= 100; + result.push_back((char)('0' + K / 10)); K %= 10; + result.push_back((char)('0' + K)); + } + else + { + jsoncons::detail::from_integer(K, result); + } + } + + template + void prettify_string(const char *buffer, std::size_t length, int k, int min_exp, int max_exp, Result& result) + { + int nb_digits = (int)length; + int offset; + /* v = buffer * 10^k + kk is such that 10^(kk-1) <= v < 10^kk + this way kk gives the position of the decimal point. + */ + int kk = nb_digits + k; + + if (nb_digits <= kk && kk <= max_exp) + { + /* the first digits are already in. Add some 0s and call it a day. */ + /* the max_exp is a personal choice. Only 16 digits could possibly be relevant. + * Basically we want to print 12340000000 rather than 1234.0e7 or 1.234e10 */ + for (int i = 0; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + for (int i = nb_digits; i < kk; ++i) + { + result.push_back('0'); + } + result.push_back('.'); + result.push_back('0'); + } + else if (0 < kk && kk <= max_exp) + { + /* comma number. Just insert a '.' at the correct location. */ + for (int i = 0; i < kk; ++i) + { + result.push_back(buffer[i]); + } + result.push_back('.'); + for (int i = kk; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + } + else if (min_exp < kk && kk <= 0) + { + offset = 2 - kk; + + result.push_back('0'); + result.push_back('.'); + for (int i = 2; i < offset; ++i) + result.push_back('0'); + for (int i = 0; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + } + else if (nb_digits == 1) + { + result.push_back(buffer[0]); + result.push_back('e'); + fill_exponent(kk - 1, result); + } + else + { + result.push_back(buffer[0]); + result.push_back('.'); + for (int i = 1; i < nb_digits; ++i) + { + result.push_back(buffer[i]); + } + result.push_back('e'); + fill_exponent(kk - 1, result); + } + } + + template + void dump_buffer(const char *buffer, std::size_t length, char decimal_point, Result& result) + { + const char *sbeg = buffer; + const char *send = sbeg + length; + + if (sbeg != send) + { + bool needs_dot = true; + for (const char* q = sbeg; q < send; ++q) + { + switch (*q) + { + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + case '+': + result.push_back(*q); + break; + case 'e': + case 'E': + result.push_back('e'); + needs_dot = false; + break; + default: + if (*q == decimal_point) + { + needs_dot = false; + result.push_back('.'); + } + break; + } + } + if (needs_dot) + { + result.push_back('.'); + result.push_back('0'); + } + } + } + + template + bool dtoa_scientific(double val, char decimal_point, Result& result) + { + if (val == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + jsoncons::detail::chars_to to_double_; + + char buffer[100]; + int precision = std::numeric_limits::digits10; + int length = snprintf(buffer, sizeof(buffer), "%1.*e", precision, val); + if (length < 0) + { + return false; + } + if (to_double_(buffer, sizeof(buffer)) != val) + { + const int precision2 = std::numeric_limits::max_digits10; + length = snprintf(buffer, sizeof(buffer), "%1.*e", precision2, val); + if (length < 0) + { + return false; + } + } + dump_buffer(buffer, static_cast(length), decimal_point, result); + return true; + } + + template + bool dtoa_general(double val, char decimal_point, Result& result, std::false_type) + { + if (val == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + jsoncons::detail::chars_to to_double_; + + char buffer[100]; + int precision = std::numeric_limits::digits10; + int length = snprintf(buffer, sizeof(buffer), "%1.*g", precision, val); + if (length < 0) + { + return false; + } + if (to_double_(buffer, sizeof(buffer)) != val) + { + const int precision2 = std::numeric_limits::max_digits10; + length = snprintf(buffer, sizeof(buffer), "%1.*g", precision2, val); + if (length < 0) + { + return false; + } + } + dump_buffer(buffer, length, decimal_point, result); + return true; + } + + template + bool dtoa_general(double v, char decimal_point, Result& result, std::true_type) + { + if (v == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + int length = 0; + int k; + + char buffer[100]; + + double u = std::signbit(v) ? -v : v; + if (jsoncons::detail::grisu3(u, buffer, &length, &k)) + { + if (std::signbit(v)) + { + result.push_back('-'); + } + // min exp: -4 is consistent with sprintf + // max exp: std::numeric_limits::max_digits10 + jsoncons::detail::prettify_string(buffer, length, k, -4, std::numeric_limits::max_digits10, result); + return true; + } + else + { + return dtoa_general(v, decimal_point, result, std::false_type()); + } + } + + template + bool dtoa_fixed(double val, char decimal_point, Result& result, std::false_type) + { + if (val == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + jsoncons::detail::chars_to to_double_; + + char buffer[100]; + int precision = std::numeric_limits::digits10; + int length = snprintf(buffer, sizeof(buffer), "%1.*f", precision, val); + if (length < 0) + { + return false; + } + if (to_double_(buffer, sizeof(buffer)) != val) + { + const int precision2 = std::numeric_limits::max_digits10; + length = snprintf(buffer, sizeof(buffer), "%1.*f", precision2, val); + if (length < 0) + { + return false; + } + } + dump_buffer(buffer, length, decimal_point, result); + return true; + } + + template + bool dtoa_fixed(double v, char decimal_point, Result& result, std::true_type) + { + if (v == 0) + { + result.push_back('0'); + result.push_back('.'); + result.push_back('0'); + return true; + } + + int length = 0; + int k; + + char buffer[100]; + + double u = std::signbit(v) ? -v : v; + if (jsoncons::detail::grisu3(u, buffer, &length, &k)) + { + if (std::signbit(v)) + { + result.push_back('-'); + } + jsoncons::detail::prettify_string(buffer, length, k, std::numeric_limits::lowest(), (std::numeric_limits::max)(), result); + return true; + } + else + { + return dtoa_fixed(v, decimal_point, result, std::false_type()); + } + } + + template + bool dtoa_fixed(double v, char decimal_point, Result& result) + { + return dtoa_fixed(v, decimal_point, result, std::integral_constant::is_iec559>()); + } + + template + bool dtoa_general(double v, char decimal_point, Result& result) + { + return dtoa_general(v, decimal_point, result, std::integral_constant::is_iec559>()); + } + + class write_double + { + private: + chars_to to_double_; + float_chars_format float_format_; + int precision_; + char decimal_point_; + public: + write_double(float_chars_format float_format, int precision) + : float_format_(float_format), precision_(precision), decimal_point_('.') + { + #if !defined(JSONCONS_NO_LOCALECONV) + struct lconv *lc = localeconv(); + if (lc != nullptr && lc->decimal_point[0] != 0) + { + decimal_point_ = lc->decimal_point[0]; + } + #endif + } + write_double(const write_double&) = default; + + write_double& operator=(const write_double&) = default; + + template + std::size_t operator()(double val, Result& result) + { + std::size_t count = 0; + + char number_buffer[200]; + int length = 0; + + switch (float_format_) + { + case float_chars_format::fixed: + { + if (precision_ > 0) + { + length = snprintf(number_buffer, sizeof(number_buffer), "%1.*f", precision_, val); + if (length < 0) + { + JSONCONS_THROW(json_runtime_error("write_double failed.")); + } + dump_buffer(number_buffer, length, decimal_point_, result); + } + else + { + if (!dtoa_fixed(val, decimal_point_, result)) + { + JSONCONS_THROW(json_runtime_error("write_double failed.")); + } + } + } + break; + case float_chars_format::scientific: + { + if (precision_ > 0) + { + length = snprintf(number_buffer, sizeof(number_buffer), "%1.*e", precision_, val); + if (length < 0) + { + JSONCONS_THROW(json_runtime_error("write_double failed.")); + } + dump_buffer(number_buffer, length, decimal_point_, result); + } + else + { + if (!dtoa_scientific(val, decimal_point_, result)) + { + JSONCONS_THROW(json_runtime_error("write_double failed.")); + } + } + } + break; + case float_chars_format::general: + { + if (precision_ > 0) + { + length = snprintf(number_buffer, sizeof(number_buffer), "%1.*g", precision_, val); + if (length < 0) + { + JSONCONS_THROW(json_runtime_error("write_double failed.")); + } + dump_buffer(number_buffer, length, decimal_point_, result); + } + else + { + if (!dtoa_general(val, decimal_point_, result)) + { + JSONCONS_THROW(json_runtime_error("write_double failed.")); + } + } + break; + } + default: + JSONCONS_THROW(json_runtime_error("write_double failed.")); + break; + } + return count; + } + }; + +} // namespace detail +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/encode_json.hpp b/third_party/jsoncons/encode_json.hpp new file mode 100644 index 0000000000..f042d3d744 --- /dev/null +++ b/third_party/jsoncons/encode_json.hpp @@ -0,0 +1,290 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_ENCODE_JSON_HPP +#define JSONCONS_ENCODE_JSON_HPP + +#include +#include +#include +#include +#include // std::basic_istream +#include +#include + +namespace jsoncons { + + // to string + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_json(const T& val, CharContainer& cont, + const basic_json_encode_options& options = + basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + using char_type = typename CharContainer::value_type; + + if (indent == indenting::no_indent) + { + basic_compact_json_encoder> encoder(cont, options); + val.dump(encoder); + } + else + { + basic_json_encoder> encoder(cont, options); + val.dump(encoder); + } + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_json(const T& val, CharContainer& cont, + const basic_json_encode_options& options = + basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + using char_type = typename CharContainer::value_type; + + if (indent == indenting::no_indent) + { + basic_compact_json_encoder> encoder(cont, options); + encode_json(val, encoder); + } + else + { + basic_json_encoder> encoder(cont, options); + encode_json(val, encoder); + } + } + + // to stream + + template + typename std::enable_if::value>::type + encode_json(const T& val, std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + if (indent == indenting::no_indent) + { + basic_compact_json_encoder encoder(os, options); + val.dump(encoder); + } + else + { + basic_json_encoder encoder(os, options); + val.dump(encoder); + } + } + + template + typename std::enable_if::value>::type + encode_json(const T& val, std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + if (indent == indenting::no_indent) + { + basic_compact_json_encoder encoder(os, options); + encode_json(val, encoder); + } + else + { + basic_json_encoder encoder(os, options); + encode_json(val, encoder); + } + } + + // to string with allocator_set + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_json(const allocator_set& alloc_set, + const T& val, CharContainer& cont, + const basic_json_encode_options& options = + basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + using char_type = typename CharContainer::value_type; + + if (indent == indenting::no_indent) + { + basic_compact_json_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + val.dump(encoder); + } + else + { + basic_json_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + val.dump(encoder); + } + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_json(const allocator_set& alloc_set, + const T& val, CharContainer& cont, + const basic_json_encode_options& options = + basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + using char_type = typename CharContainer::value_type; + + if (indent == indenting::no_indent) + { + basic_compact_json_encoder,TempAllocator> encoder(cont, options, + alloc_set.get_temp_allocator()); + encode_json(val, encoder); + } + else + { + basic_json_encoder, TempAllocator> encoder(cont, options, + alloc_set.get_temp_allocator()); + encode_json(val, encoder); + } + } + + // to stream with allocator_set + + template + typename std::enable_if::value>::type + encode_json(const allocator_set& alloc_set, + const T& val, std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + if (indent == indenting::no_indent) + { + basic_compact_json_encoder encoder(os, options, alloc_set.get_temp_allocator()); + val.dump(encoder); + } + else + { + basic_json_encoder encoder(os, options, alloc_set.get_temp_allocator()); + val.dump(encoder); + } + } + + template + typename std::enable_if::value>::type + encode_json(const allocator_set& alloc_set, + const T& val, std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_encode_options(), + indenting indent = indenting::no_indent) + { + if (indent == indenting::no_indent) + { + basic_compact_json_encoder encoder(os, options, alloc_set.get_temp_allocator()); + encode_json(val, encoder); + } + else + { + basic_json_encoder encoder(os, options, alloc_set.get_temp_allocator()); + encode_json(val, encoder); + } + } + + // to encoder + + template + void encode_json(const T& val, basic_json_visitor& encoder) + { + std::error_code ec; + encode_traits::encode(val, encoder, basic_json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + encoder.flush(); + } + + // encode_json_pretty + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_json_pretty(const T& val, + CharContainer& cont, + const basic_json_encode_options& options = basic_json_encode_options()) + { + using char_type = typename CharContainer::value_type; + + basic_json_encoder> encoder(cont, options); + val.dump(encoder); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_json_pretty(const T& val, + CharContainer& cont, + const basic_json_encode_options& options = basic_json_encode_options()) + { + using char_type = typename CharContainer::value_type; + basic_json_encoder> encoder(cont, options); + encode_json(val, encoder); + } + + template + typename std::enable_if::value>::type + encode_json_pretty(const T& val, + std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_encode_options()) + { + basic_json_encoder encoder(os, options); + val.dump(encoder); + } + + template + typename std::enable_if::value>::type + encode_json_pretty(const T& val, + std::basic_ostream& os, + const basic_json_encode_options& options = basic_json_encode_options()) + { + basic_json_encoder encoder(os, options); + encode_json(val, encoder); + } + +// legacy + + template + void encode_json(const T& val, CharContainer& cont, indenting indent) + { + if (indent == indenting::indent) + { + encode_json_pretty(val,cont); + } + else + { + encode_json(val,cont); + } + } + + template + void encode_json(const T& val, + std::basic_ostream& os, + indenting indent) + { + if (indent == indenting::indent) + { + encode_json_pretty(val, os); + } + else + { + encode_json(val, os); + } + } + +//end legacy + +} // jsoncons + +#endif + diff --git a/third_party/jsoncons/encode_traits.hpp b/third_party/jsoncons/encode_traits.hpp new file mode 100644 index 0000000000..ff9b66a7b1 --- /dev/null +++ b/third_party/jsoncons/encode_traits.hpp @@ -0,0 +1,378 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_ENCODE_TRAITS_HPP +#define JSONCONS_ENCODE_TRAITS_HPP + +#include +#include +#include +#include +#include // std::enable_if, std::true_type, std::false_type +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + + // encode_traits + + template + struct encode_traits + { + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + encode(std::integral_constant::value>(), + val, encoder, proto, ec); + } + private: + template + static void encode(std::true_type, + const T& val, + basic_json_visitor& encoder, + const Json& /*proto*/, + std::error_code& ec) + { + auto j = json_type_traits::to_json(val); + j.dump(encoder, ec); + } + template + static void encode(std::false_type, + const T& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + auto j = json_type_traits::to_json(val, proto.get_allocator()); + j.dump(encoder, ec); + } + }; + + // specializations + + // bool + template + struct encode_traits::value + >::type> + { + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json&, + std::error_code& ec) + { + encoder.bool_value(val,semantic_tag::none,ser_context(),ec); + } + }; + + // uint + template + struct encode_traits::value + >::type> + { + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json&, + std::error_code& ec) + { + encoder.uint64_value(val,semantic_tag::none,ser_context(),ec); + } + }; + + // int + template + struct encode_traits::value + >::type> + { + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json&, + std::error_code& ec) + { + encoder.int64_value(val,semantic_tag::none,ser_context(),ec); + } + }; + + // float or double + template + struct encode_traits::value + >::type> + { + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json&, + std::error_code& ec) + { + encoder.double_value(val,semantic_tag::none,ser_context(),ec); + } + }; + + // string + template + struct encode_traits::value && + std::is_same::value + >::type> + { + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json&, + std::error_code& ec) + { + encoder.string_value(val,semantic_tag::none,ser_context(),ec); + } + }; + template + struct encode_traits::value && + !std::is_same::value + >::type> + { + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json&, + std::error_code& ec) + { + std::basic_string s; + unicode_traits::convert(val.data(), val.size(), s); + encoder.string_value(s,semantic_tag::none,ser_context(),ec); + } + }; + + // std::pair + + template + struct encode_traits, CharT> + { + using value_type = std::pair; + + template + static void encode(const value_type& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + encoder.begin_array(2,semantic_tag::none,ser_context(),ec); + if (ec) return; + encode_traits::encode(val.first, encoder, proto, ec); + if (ec) return; + encode_traits::encode(val.second, encoder, proto, ec); + if (ec) return; + encoder.end_array(ser_context(),ec); + } + }; + + // std::tuple + + namespace detail + { + template + struct json_serialize_tuple_helper + { + using char_type = typename Json::char_type; + using element_type = typename std::tuple_element::type; + using next = json_serialize_tuple_helper; + + static void encode(const Tuple& tuple, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + encode_traits::encode(std::get(tuple), encoder, proto, ec); + if (ec) return; + next::encode(tuple, encoder, proto, ec); + } + }; + + template + struct json_serialize_tuple_helper<0, Size, Json, Tuple> + { + using char_type = typename Json::char_type; + static void encode(const Tuple&, + basic_json_visitor&, + const Json&, + std::error_code&) + { + } + }; + } // namespace detail + + + template + struct encode_traits, CharT> + { + using value_type = std::tuple; + static constexpr std::size_t size = sizeof...(E); + + template + static void encode(const value_type& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + using helper = jsoncons::detail::json_serialize_tuple_helper>; + encoder.begin_array(size,semantic_tag::none,ser_context(),ec); + if (ec) return; + helper::encode(val, encoder, proto, ec); + if (ec) return; + encoder.end_array(ser_context(),ec); + } + }; + + // vector like + template + struct encode_traits::value && + extension_traits::is_array_like::value && + !extension_traits::is_typed_array::value + >::type> + { + using value_type = typename T::value_type; + + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + encoder.begin_array(val.size(),semantic_tag::none,ser_context(),ec); + if (ec) return; + for (auto it = std::begin(val); it != std::end(val); ++it) + { + encode_traits::encode(*it, encoder, proto, ec); + if (ec) return; + } + encoder.end_array(ser_context(), ec); + } + }; + + template + struct encode_traits::value && + extension_traits::is_array_like::value && + extension_traits::is_typed_array::value + >::type> + { + using value_type = typename T::value_type; + + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json&, + std::error_code& ec) + { + encoder.typed_array(jsoncons::span(val), semantic_tag::none, ser_context(), ec); + } + }; + + // std::array + + template + struct encode_traits,CharT> + { + using value_type = typename std::array::value_type; + + template + static void encode(const std::array& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + encoder.begin_array(val.size(),semantic_tag::none,ser_context(),ec); + if (ec) return; + for (auto it = std::begin(val); it != std::end(val); ++it) + { + encode_traits::encode(*it, encoder, proto, ec); + if (ec) return; + } + encoder.end_array(ser_context(),ec); + } + }; + + // map like + + template + struct encode_traits::value && + extension_traits::is_map_like::value && + extension_traits::is_constructible_from_const_pointer_and_size::value + >::type> + { + using mapped_type = typename T::mapped_type; + using value_type = typename T::value_type; + using key_type = typename T::key_type; + + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + encoder.begin_object(val.size(), semantic_tag::none, ser_context(), ec); + if (ec) return; + for (auto it = std::begin(val); it != std::end(val); ++it) + { + encoder.key(it->first); + encode_traits::encode(it->second, encoder, proto, ec); + if (ec) return; + } + encoder.end_object(ser_context(), ec); + if (ec) return; + } + }; + + template + struct encode_traits::value && + extension_traits::is_map_like::value && + std::is_integral::value + >::type> + { + using mapped_type = typename T::mapped_type; + using value_type = typename T::value_type; + using key_type = typename T::key_type; + + template + static void encode(const T& val, + basic_json_visitor& encoder, + const Json& proto, + std::error_code& ec) + { + encoder.begin_object(val.size(), semantic_tag::none, ser_context(), ec); + if (ec) return; + for (auto it = std::begin(val); it != std::end(val); ++it) + { + std::basic_string s; + jsoncons::detail::from_integer(it->first,s); + encoder.key(s); + encode_traits::encode(it->second, encoder, proto, ec); + if (ec) return; + } + encoder.end_object(ser_context(), ec); + if (ec) return; + } + }; + +} // jsoncons + +#endif + diff --git a/third_party/jsoncons/extension_traits.hpp b/third_party/jsoncons/extension_traits.hpp new file mode 100644 index 0000000000..c69f77a648 --- /dev/null +++ b/third_party/jsoncons/extension_traits.hpp @@ -0,0 +1,928 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_EXTENSION_TRAITS_HPP +#define JSONCONS_EXTENSION_TRAITS_HPP + +#include +#include +#include +#include // std::enable_if, std::true_type +#include +#include // std::iterator_traits +#include +#include // std::array +#include // std::byte +#include // std::declval +#include // CHAR_BIT +#include + +#if defined(JSONCONS_HAS_POLYMORPHIC_ALLOCATOR) +#include +#endif + +namespace jsoncons { +namespace extension_traits { + + // is_char8 + template + struct is_char8 : std::false_type {}; + + template + struct is_char8::value && + !std::is_same::value && + sizeof(uint8_t) == sizeof(CharT)>::type> : std::true_type {}; + + // is_char16 + template + struct is_char16 : std::false_type {}; + + template + struct is_char16::value && + !std::is_same::value && + (std::is_same::value || sizeof(uint16_t) == sizeof(CharT))>::type> : std::true_type {}; + + // is_char32 + template + struct is_char32 : std::false_type {}; + + template + struct is_char32::value && + !std::is_same::value && + (std::is_same::value || (!std::is_same::value && sizeof(uint32_t) == sizeof(CharT)))>::type> : std::true_type {}; + + // is_int128 + + template + struct is_int128_type : std::false_type {}; + +#if defined(JSONCONS_HAS_INT128) + template + struct is_int128_type::value>::type> : std::true_type {}; +#endif + + // is_unsigned_integer + + template + struct is_uint128_type : std::false_type {}; + +#if defined (JSONCONS_HAS_INT128) + template + struct is_uint128_type::value>::type> : std::true_type {}; +#endif + + template + class integer_limits + { + public: + static constexpr bool is_specialized = false; + }; + + template + class integer_limits::value && !std::is_same::value>::type> + { + public: + static constexpr bool is_specialized = true; + static constexpr bool is_signed = std::numeric_limits::is_signed; + static constexpr int digits = std::numeric_limits::digits; + static constexpr std::size_t buffer_size = static_cast(sizeof(T)*CHAR_BIT*0.302) + 3; + + static constexpr T(max)() noexcept + { + return (std::numeric_limits::max)(); + } + static constexpr T(min)() noexcept + { + return (std::numeric_limits::min)(); + } + static constexpr T lowest() noexcept + { + return std::numeric_limits::lowest(); + } + }; + + template + class integer_limits::value && is_int128_type::value>::type> + { + public: + static constexpr bool is_specialized = true; + static constexpr bool is_signed = true; + static constexpr int digits = sizeof(T)*CHAR_BIT - 1; + static constexpr std::size_t buffer_size = (sizeof(T)*CHAR_BIT*0.302) + 3; + + static constexpr T(max)() noexcept + { + return (((((T)1 << (digits - 1)) - 1) << 1) + 1); + } + static constexpr T(min)() noexcept + { + return -(max)() - 1; + } + static constexpr T lowest() noexcept + { + return (min)(); + } + }; + + template + class integer_limits::value && is_uint128_type::value>::type> + { + public: + static constexpr bool is_specialized = true; + static constexpr bool is_signed = false; + static constexpr int digits = sizeof(T)*CHAR_BIT; + + static constexpr T(max)() noexcept + { + return T(T(~0)); + } + static constexpr T(min)() noexcept + { + return 0; + } + static constexpr T lowest() noexcept + { + return std::numeric_limits::lowest(); + } + }; + + #ifndef JSONCONS_HAS_VOID_T + // follows https://en.cppreference.com/w/cpp/types/void_t + template struct make_void { typedef void type;}; + template using void_t = typename make_void::type; + #else + using void_t = std::void_t; + #endif + + // follows http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4436.pdf + + // detector + + // primary template handles all types not supporting the archetypal Op + template< + class Default, + class, // always void; supplied externally + template class Op, + typename... Args + > + struct detector + { + constexpr static auto value = false; + using type = Default; + }; + + // specialization recognizes and handles only types supporting Op + template< + class Default, + template class Op, + typename... Args + > + struct detector>, Op, Args...> + { + constexpr static auto value = true; + using type = Op; + }; + + // is_detected, is_detected_t + + template< template class Op,typename... Args > + using + is_detected = detector; + + template< template class Op,typename... Args > + using + is_detected_t = typename is_detected::type; + + // detected_or, detected_or_t + + template class Op,typename... Args > + using + detected_or = detector; + + template class Op,typename... Args > + using + detected_or_t = typename detected_or::type; + + // is_detected_exact + + template< class Expected, template class Op,typename... Args > + using + is_detected_exact = std::is_same< Expected, is_detected_t >; + + // is_detected_convertible + + template< class To, template class Op,typename... Args > + using + is_detected_convertible = std::is_convertible< is_detected_t, To >; + + template + struct is_stateless + : public std::integral_constant::value && + std::is_empty::value)> + {}; + + // to_plain_pointer + + template inline + typename std::pointer_traits::element_type* to_plain_pointer(Pointer ptr) + { + return (std::addressof(*ptr)); + } + + template inline + T * to_plain_pointer(T * ptr) + { + return (ptr); + } + + // is_std_byte + + template + struct is_std_byte : std::false_type {}; +#if defined(JSONCONS_HAS_STD_BYTE) + template + struct is_std_byte::value + >::type> : std::true_type {}; +#endif + // is_byte + + template + struct is_byte : std::false_type {}; + + template + struct is_byte::value || + std::is_same::value || + std::is_same::value || + is_std_byte::value + >::type> : std::true_type {}; + + // is_character + + template + struct is_character : std::false_type {}; + + template + struct is_character::value || +#ifdef __cpp_char8_t + std::is_same::value || +#endif + std::is_same::value + >::type> : std::true_type {}; + + // is_narrow_character + + template + struct is_narrow_character : std::false_type {}; + + template + struct is_narrow_character::value && (sizeof(T) == sizeof(char)) + >::type> : std::true_type {}; + + // is_wide_character + + template + struct is_wide_character : std::false_type {}; + + template + struct is_wide_character::value && (sizeof(T) != sizeof(char)) + >::type> : std::true_type {}; + + // From boost + namespace ut_detail { + + template + struct is_cstring_impl : public std::false_type {}; + + template + struct is_cstring_impl : public is_cstring_impl {}; + + template + struct is_cstring_impl : public is_cstring_impl {}; + + template<> + struct is_cstring_impl : public std::true_type {}; + +#ifdef __cpp_char8_t + template<> + struct is_cstring_impl : public std::true_type {}; +#endif + + template<> + struct is_cstring_impl : public std::true_type {}; + + } // namespace ut_detail + + template + struct is_cstring : public ut_detail::is_cstring_impl::type> {}; + + // is_bool + + template + struct is_bool : std::false_type {}; + + template + struct is_bool::value + >::type> : std::true_type {}; + + // is_u8_u16_u32_or_u64 + + template + struct is_u8_u16_u32_or_u64 : std::false_type {}; + + template + struct is_u8_u16_u32_or_u64::value || + std::is_same::value || + std::is_same::value || + std::is_same::value + >::type> : std::true_type {}; + + // is_int + + template + struct is_i8_i16_i32_or_i64 : std::false_type {}; + + template + struct is_i8_i16_i32_or_i64::value || + std::is_same::value || + std::is_same::value || + std::is_same::value + >::type> : std::true_type {}; + + // is_float_or_double + + template + struct is_float_or_double : std::false_type {}; + + template + struct is_float_or_double::value || + std::is_same::value + >::type> : std::true_type {}; + + // make_unsigned + template + struct make_unsigned_impl {using type = typename std::make_unsigned::type;}; + + #if defined(JSONCONS_HAS_INT128) + template <> + struct make_unsigned_impl {using type = uint128_type;}; + template <> + struct make_unsigned_impl {using type = uint128_type;}; + #endif + + template + struct make_unsigned + : make_unsigned_impl::type> + {}; + + // is_integer + + template + struct is_integer : std::false_type {}; + + template + struct is_integer::is_specialized>::type> : std::true_type {}; + + // is_signed_integer + + template + struct is_signed_integer : std::false_type {}; + + template + struct is_signed_integer::is_specialized && + integer_limits::is_signed>::type> : std::true_type {}; + + // is_unsigned_integer + + template + struct is_unsigned_integer : std::false_type {}; + + template + struct is_unsigned_integer::is_specialized && + !integer_limits::is_signed>::type> : std::true_type {}; + + // is_primitive + + template + struct is_primitive : std::false_type {}; + + template + struct is_primitive::value || + is_bool::value || + std::is_floating_point::value + >::type> : std::true_type {}; + + // Containers + + template + using + container_npos_t = decltype(Container::npos); + + template + using + container_allocator_type_t = typename Container::allocator_type; + + template + using + container_mapped_type_t = typename Container::mapped_type; + + template + using + container_key_type_t = typename Container::key_type; + + template + using + container_value_type_t = typename std::iterator_traits::value_type; + + template + using + container_char_traits_t = typename Container::traits_type::char_type; + + template + using + container_push_back_t = decltype(std::declval().push_back(std::declval())); + + template + using + container_push_front_t = decltype(std::declval().push_front(std::declval())); + + template + using + container_insert_t = decltype(std::declval().insert(std::declval())); + + template + using + container_reserve_t = decltype(std::declval().reserve(typename Container::size_type())); + + template + using + container_data_t = decltype(std::declval().data()); + + template + using + container_size_t = decltype(std::declval().size()); + + // has_allocator_type + + template + struct has_allocator_type : std::false_type {}; + + template + struct has_allocator_type::value + >::type> : std::true_type {}; + + // is_string_or_string_view + + template + struct is_string_or_string_view : std::false_type {}; + + template + struct is_string_or_string_view::value && + is_detected_exact::value && + is_detected::value + >::type> : std::true_type {}; + + // is_string + + template + struct is_string : std::false_type {}; + + template + struct is_string::value && + has_allocator_type::value + >::type> : std::true_type {}; + + // is_string_view + + template + struct is_string_view : std::false_type {}; + + template + struct is_string_view::value && + !is_detected::value + >::type> : std::true_type {}; + + // is_map_like + + template + struct is_map_like : std::false_type {}; + + template + struct is_map_like::value && + is_detected::value && + is_detected::value && + is_detected::value + >::type> + : std::true_type {}; + + // is_std_array + template + struct is_std_array : std::false_type {}; + + template + struct is_std_array> : std::true_type {}; + + // is_array_like + + template + struct is_array_like : std::false_type {}; + + template + struct is_array_like::value && + is_detected::value && + !is_std_array::value && + !is_detected_exact::value && + !is_map_like::value + >::type> + : std::true_type {}; + + // is_constructible_from_const_pointer_and_size + + template + struct is_constructible_from_const_pointer_and_size : std::false_type {}; + + template + struct is_constructible_from_const_pointer_and_size::value + >::type> + : std::true_type {}; + + // has_reserve + + template + using + has_reserve = is_detected; + + // is_back_insertable + + template + using + is_back_insertable = is_detected; + + // is_front_insertable + + template + using + is_front_insertable = is_detected; + + // is_insertable + + template + using + is_insertable = is_detected; + + // has_data, has_data_exact + + template + using + has_data = is_detected; + + template + using + has_data_exact = is_detected_exact; + + // has_size + + template + using + has_size = is_detected; + + // has_data_and_size + + template + struct has_data_and_size + { + static constexpr bool value = has_data::value && has_size::value; + }; + + // is_byte_sequence + + template + struct is_byte_sequence : std::false_type {}; + + template + struct is_byte_sequence::value && + has_size::value && + is_byte::value + >::type> : std::true_type {}; + + // is_char_sequence + + template + struct is_char_sequence : std::false_type {}; + + template + struct is_char_sequence::value && + has_size::value && + is_character::value + >::type> : std::true_type {}; + + // is_sequence_of + + template + struct is_sequence_of : std::false_type {}; + + template + struct is_sequence_of::value && + has_size::value && + std::is_same::value + >::type> : std::true_type {}; + + // is_back_insertable_byte_container + + template + struct is_back_insertable_byte_container : std::false_type {}; + + template + struct is_back_insertable_byte_container::value && + is_byte::value + >::type> : std::true_type {}; + + // is_back_insertable_char_container + + template + struct is_back_insertable_char_container : std::false_type {}; + + template + struct is_back_insertable_char_container::value && + is_character::value + >::type> : std::true_type {}; + + // is_back_insertable_container_of + + template + struct is_back_insertable_container_of : std::false_type {}; + + template + struct is_back_insertable_container_of::value && + std::is_same::value + >::type> : std::true_type {}; + + // is_c_array + + template + struct is_c_array : std::false_type {}; + + template + struct is_c_array : std::true_type {}; + + template + struct is_c_array : std::true_type {}; + +namespace impl { + + template + struct is_typed_array : std::false_type {}; + + template + struct is_typed_array + < + T, + typename std::enable_if::value && + (std::is_same::type,uint8_t>::value || + std::is_same::type,uint16_t>::value || + std::is_same::type,uint32_t>::value || + std::is_same::type,uint64_t>::value || + std::is_same::type,int8_t>::value || + std::is_same::type,int16_t>::value || + std::is_same::type,int32_t>::value || + std::is_same::type,int64_t>::value || + std::is_same::type,float_t>::value || + std::is_same::type,double_t>::value)>::type + > : std::true_type{}; + +} // namespace impl + + template + using is_typed_array = impl::is_typed_array::type>; + + // is_compatible_element + + template + struct is_compatible_element : std::false_type {}; + + template + struct is_compatible_element + < + Container, Element, + typename std::enable_if::value>::type> + : std::is_convertible< typename std::remove_pointer().data() )>::type(*)[], Element(*)[]> + {}; + + template + using + construct_from_string_t = decltype(T(std::string{})); + + + template + using + is_constructible_from_string = is_detected; + + template + using + construct_from_data_size_t = decltype(T(static_cast(nullptr),Size{})); + + + template + using + is_constructible_from_data_size = is_detected; + + // is_unary_function_object + // is_unary_function_object_exact + + template + using + unary_function_object_t = decltype(std::declval()(std::declval())); + + template + using + is_unary_function_object = is_detected; + + template + using + is_unary_function_object_exact = is_detected_exact; + + // is_binary_function_object + // is_binary_function_object_exact + + template + using + binary_function_object_t = decltype(std::declval()(std::declval(),std::declval())); + + template + using + is_binary_function_object = is_detected; + + template + using + is_binary_function_object_exact = is_detected_exact; + + template + struct is_convertible_to_string_view : std::false_type {}; + + template + struct is_convertible_to_string_view::value || + is_cstring::value + >::type> : std::true_type {}; + + #if defined(JSONCONS_HAS_2017) + template + using is_nothrow_swappable = std::is_nothrow_swappable; + #else + template + struct is_nothrow_swappable { + static const bool value = noexcept(swap(std::declval(), std::declval())); + }; + #endif + + #if defined(JSONCONS_HAS_2014) + template + using alignment_of = std::alignment_of; + + template< typename T, T... Ints > + using integer_sequence = std::integer_sequence; + + template + using index_sequence = std::index_sequence; + + template + using make_integer_sequence = std::make_integer_sequence; + + template + using make_index_sequence = std::make_index_sequence; + + template + using index_sequence_for = std::index_sequence_for; + + #else + template + struct alignment_of + : std::integral_constant::type)> {}; + + template + class integer_sequence + { + public: + using value_type = T; + static_assert(std::is_integral::value, "not integral type"); + static constexpr std::size_t size() noexcept + { + return sizeof...(Ints); + } + }; + + template + using index_sequence = integer_sequence; + namespace detail_ { + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0 && Begin < End, "unexpected argument (Begin<0 || Begin<=End)"); + + template + struct IntSeqCombiner; + + template + struct IntSeqCombiner, integer_sequence> { + using TResult = integer_sequence; + }; + + using TResult = + typename IntSeqCombiner::TResult, + typename IntSeqImpl::TResult>::TResult; + }; + + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; + }; + + template + struct IntSeqImpl { + using TValue = T; + static_assert(std::is_integral::value, "not integral type"); + static_assert(Begin >= 0, "unexpected argument (Begin<0)"); + using TResult = integer_sequence; + }; + } // namespace detail_ + + template + using make_integer_sequence = typename detail_::IntSeqImpl::TResult; + + template + using make_index_sequence = make_integer_sequence; + + template + using index_sequence_for = make_index_sequence; + + + #endif + + // is_propagating_allocator + + template + using + allocator_outer_allocator_type_t = typename Allocator::outer_allocator_type; + + template + using + allocator_inner_allocator_type_t = typename Allocator::inner_allocator_type; + + template + struct is_propagating_allocator : std::false_type {}; + + template + struct is_polymorphic_allocator : std::false_type {}; + +#if defined(JSONCONS_HAS_POLYMORPHIC_ALLOCATOR) + template + struct is_polymorphic_allocator + < + T, + typename std::enable_if<(std::is_same>::value) >::type + > : std::true_type{}; +#endif + template + struct is_propagating_allocator + < + T, + typename std::enable_if<(is_polymorphic_allocator::value) || + (is_detected::value && is_detected::value)>::type + > : std::true_type{}; + +} // extension_traits +} // jsoncons + +#endif diff --git a/third_party/jsoncons/item_event_visitor.hpp b/third_party/jsoncons/item_event_visitor.hpp new file mode 100644 index 0000000000..f6db184c12 --- /dev/null +++ b/third_party/jsoncons/item_event_visitor.hpp @@ -0,0 +1,2080 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_ITEM_EVENT_VISITOR_HPP +#define JSONCONS_ITEM_EVENT_VISITOR_HPP + +#include +#include + +namespace jsoncons { + + template > + class basic_item_event_visitor_to_json_visitor; + + template + class basic_item_event_visitor + { + template + friend class basic_item_event_visitor_to_json_visitor; + public: + using char_type = CharT; + using char_traits_type = std::char_traits; + + using string_view_type = jsoncons::basic_string_view; + + basic_item_event_visitor(basic_item_event_visitor&&) = default; + + basic_item_event_visitor& operator=(basic_item_event_visitor&&) = default; + + basic_item_event_visitor() = default; + + virtual ~basic_item_event_visitor() noexcept = default; + + void flush() + { + visit_flush(); + } + + bool begin_object(semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_object(tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_object(std::size_t length, + semantic_tag tag=semantic_tag::none, + const ser_context& context = ser_context()) + { + std::error_code ec; + bool more = visit_begin_object(length, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool end_object(const ser_context& context = ser_context()) + { + std::error_code ec; + bool more = visit_end_object(context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_array(semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_array(tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_array(std::size_t length, + semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_array(length, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool end_array(const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_end_array(context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool key(const string_view_type& name, const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_string(name, semantic_tag::none, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool null_value(semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_null(tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool bool_value(bool value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_bool(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool string_value(const string_view_type& value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_string(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + template + bool byte_string_value(const Source& b, + semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context(), + typename std::enable_if::value,int>::type = 0) + { + std::error_code ec; + bool more = visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + template + bool byte_string_value(const Source& b, + uint64_t ext_tag, + const ser_context& context=ser_context(), + typename std::enable_if::value,int>::type = 0) + { + std::error_code ec; + bool more = visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), ext_tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool uint64_value(uint64_t value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_uint64(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool int64_value(int64_t value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_int64(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool half_value(uint16_t value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_half(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool double_value(double value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_double(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_object(semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_object(tag, context, ec); + } + + bool begin_object(std::size_t length, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_object(length, tag, context, ec); + } + + bool end_object(const ser_context& context, std::error_code& ec) + { + return visit_end_object(context, ec); + } + + bool begin_array(semantic_tag tag, const ser_context& context, std::error_code& ec) + { + return visit_begin_array(tag, context, ec); + } + + bool begin_array(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) + { + return visit_begin_array(length, tag, context, ec); + } + + bool end_array(const ser_context& context, std::error_code& ec) + { + return visit_end_array(context, ec); + } + + bool key(const string_view_type& name, const ser_context& context, std::error_code& ec) + { + return visit_string(name, semantic_tag::none, context, ec); + } + + bool null_value(semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_null(tag, context, ec); + } + + bool bool_value(bool value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_bool(value, tag, context, ec); + } + + bool string_value(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_string(value, tag, context, ec); + } + + template + bool byte_string_value(const Source& b, + semantic_tag tag, + const ser_context& context, + std::error_code& ec, + typename std::enable_if::value,int>::type = 0) + { + return visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), tag, context, ec); + } + + template + bool byte_string_value(const Source& b, + uint64_t ext_tag, + const ser_context& context, + std::error_code& ec, + typename std::enable_if::value,int>::type = 0) + { + return visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), ext_tag, context, ec); + } + + bool uint64_value(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_uint64(value, tag, context, ec); + } + + bool int64_value(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_int64(value, tag, context, ec); + } + + bool half_value(uint16_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_half(value, tag, context, ec); + } + + bool double_value(double value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_double(value, tag, context, ec); + } + + template + bool typed_array(const jsoncons::span& data, + semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_typed_array(data, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + template + bool typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_typed_array(data, tag, context, ec); + } + + bool typed_array(half_arg_t, const jsoncons::span& s, + semantic_tag tag = semantic_tag::none, + const ser_context& context = ser_context()) + { + std::error_code ec; + bool more = visit_typed_array(half_arg, s, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool typed_array(half_arg_t, const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_typed_array(half_arg, s, tag, context, ec); + } + + bool begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag = semantic_tag::multi_dim_row_major, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_multi_dim(shape, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_multi_dim(shape, tag, context, ec); + } + + bool end_multi_dim(const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_end_multi_dim(context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool end_multi_dim(const ser_context& context, + std::error_code& ec) + { + return visit_end_multi_dim(context, ec); + } + + private: + + virtual void visit_flush() = 0; + + virtual bool visit_begin_object(semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_begin_object(std::size_t /*length*/, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_object(tag, context, ec); + } + + virtual bool visit_end_object(const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_begin_array(semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_begin_array(std::size_t /*length*/, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_array(tag, context, ec); + } + + virtual bool visit_end_array(const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_null(semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_bool(bool value, + semantic_tag tag, + const ser_context& context, + std::error_code&) = 0; + + virtual bool visit_string(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_byte_string(const byte_string_view& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_byte_string(const byte_string_view& value, + uint64_t /*ext_tag*/, + const ser_context& context, + std::error_code& ec) + { + return visit_byte_string(value, semantic_tag::none, context, ec); + } + + virtual bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_half(uint16_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_double(binary::decode_half(value), + tag, + context, + ec); + } + + virtual bool visit_double(double value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(half_arg_t, + const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = half_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = double_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = double_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = visit_begin_array(2, tag, context, ec); + if (more) + { + more = visit_begin_array(shape.size(), tag, context, ec); + for (auto it = shape.begin(); more && it != shape.end(); ++it) + { + visit_uint64(*it, semantic_tag::none, context, ec); + } + if (more) + { + more = visit_end_array(context, ec); + } + } + return more; + } + + virtual bool visit_end_multi_dim(const ser_context& context, + std::error_code& ec) + { + return visit_end_array(context, ec); + } + }; + + template + class basic_item_event_visitor_to_json_visitor : public basic_item_event_visitor + { + public: + using typename basic_item_event_visitor::char_type; + using typename basic_item_event_visitor::string_view_type; + private: + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + using string_type = std::basic_string,char_allocator_type>; + + enum class container_t {root, array, object}; + enum class target_t {destination, buffer}; + + struct level + { + private: + target_t state_; + container_t type_; + int even_odd_; + std::size_t count_; + public: + + level(target_t state, container_t type) noexcept + : state_(state), type_(type), even_odd_(type == container_t::object ? 0 : 1), count_(0) + { + } + + void advance() + { + if (!is_key()) + { + ++count_; + } + if (is_object()) + { + even_odd_ = !even_odd_; + } + } + + bool is_key() const + { + return even_odd_ == 0; + } + + bool is_object() const + { + return type_ == container_t::object; + } + + target_t target() const + { + return state_; + } + + std::size_t count() const + { + return count_; + } + }; + using level_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + basic_default_json_visitor default_visitor_; + basic_json_visitor* destination_; + string_type key_; + string_type key_buffer_; + std::vector level_stack_; + + const std::basic_string null_constant = {'n','u','l','l'}; + const std::basic_string true_constant = { 't','r','u','e' }; + const std::basic_string false_constant = { 'f', 'a', 'l', 's', 'e' }; + + // noncopyable and nonmoveable + basic_item_event_visitor_to_json_visitor(const basic_item_event_visitor_to_json_visitor&) = delete; + basic_item_event_visitor_to_json_visitor& operator=(const basic_item_event_visitor_to_json_visitor&) = delete; + public: + explicit basic_item_event_visitor_to_json_visitor(const Allocator& alloc = Allocator()) + : default_visitor_(), destination_(std::addressof(default_visitor_)), + key_(alloc), key_buffer_(alloc), level_stack_(alloc) + { + level_stack_.emplace_back(target_t::destination,container_t::root); // root + } + + explicit basic_item_event_visitor_to_json_visitor(basic_json_visitor& visitor, + const Allocator& alloc = Allocator()) + : destination_(std::addressof(visitor)), + key_(alloc), key_buffer_(alloc), level_stack_(alloc) + { + level_stack_.emplace_back(target_t::destination,container_t::root); // root + } + + void reset() + { + key_.clear(); + key_buffer_.clear(); + level_stack_.clear(); + level_stack_.emplace_back(target_t::destination,container_t::root); // root + } + + basic_json_visitor& destination() + { + return *destination_; + } + + void destination(basic_json_visitor& dest) + { + destination_ = std::addressof(dest); + } + + private: + void visit_flush() override + { + destination_->flush(); + } + + bool visit_begin_object(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key()) + { + if (level_stack_.back().target() == target_t::buffer && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + level_stack_.emplace_back(target_t::buffer, container_t::object); + key_buffer_.push_back('{'); + return true; + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + level_stack_.emplace_back(target_t::buffer, container_t::object); + key_buffer_.push_back('{'); + return true; + default: + level_stack_.emplace_back(target_t::destination, container_t::object); + return destination_->begin_object(tag, context, ec); + } + } + } + + bool visit_begin_object(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key()) + { + if (level_stack_.back().target() == target_t::buffer && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + level_stack_.emplace_back(target_t::buffer, container_t::object); + key_buffer_.push_back('{'); + return true; + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + level_stack_.emplace_back(target_t::buffer, container_t::object); + key_buffer_.push_back('{'); + return true; + default: + level_stack_.emplace_back(target_t::destination, container_t::object); + return destination_->begin_object(length, tag, context, ec); + } + } + } + + bool visit_end_object(const ser_context& context, std::error_code& ec) override + { + bool retval = true; + switch (level_stack_.back().target()) + { + case target_t::buffer: + key_buffer_.push_back('}'); + JSONCONS_ASSERT(level_stack_.size() > 1); + level_stack_.pop_back(); + + if (level_stack_.back().target() == target_t::destination) + { + retval = destination_->key(key_buffer_,context, ec); + key_buffer_.clear(); + } + else if (level_stack_.back().is_key()) + { + key_buffer_.push_back(':'); + } + level_stack_.back().advance(); + break; + default: + JSONCONS_ASSERT(level_stack_.size() > 1); + level_stack_.pop_back(); + level_stack_.back().advance(); + retval = destination_->end_object(context, ec); + break; + } + return retval; + } + + bool visit_begin_array(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key()) + { + if (level_stack_.back().target() == target_t::buffer && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + level_stack_.emplace_back(target_t::buffer, container_t::array); + key_buffer_.push_back('['); + return true; + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + level_stack_.emplace_back(target_t::buffer, container_t::array); + key_buffer_.push_back('['); + return true; + default: + level_stack_.emplace_back(target_t::destination, container_t::array); + return destination_->begin_array(tag, context, ec); + } + } + } + + bool visit_begin_array(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key()) + { + if (level_stack_.back().target() == target_t::buffer && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + level_stack_.emplace_back(target_t::buffer, container_t::array); + key_buffer_.push_back('['); + return true; + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + level_stack_.emplace_back(target_t::buffer, container_t::array); + key_buffer_.push_back('['); + return true; + default: + level_stack_.emplace_back(target_t::destination, container_t::array); + return destination_->begin_array(length, tag, context, ec); + } + } + } + + bool visit_end_array(const ser_context& context, std::error_code& ec) override + { + bool retval = true; + switch (level_stack_.back().target()) + { + case target_t::buffer: + key_buffer_.push_back(']'); + JSONCONS_ASSERT(level_stack_.size() > 1); + level_stack_.pop_back(); + if (level_stack_.back().target() == target_t::destination) + { + retval = destination_->key(key_buffer_, context, ec); + key_buffer_.clear(); + } + else if (level_stack_.back().is_key()) + { + key_buffer_.push_back(':'); + } + level_stack_.back().advance(); + break; + default: + JSONCONS_ASSERT(level_stack_.size() > 1); + level_stack_.pop_back(); + level_stack_.back().advance(); + retval = destination_->end_array(context, ec); + break; + } + return retval; + } + + bool visit_string(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool retval; + + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.push_back('\"'); + key_buffer_.insert(key_buffer_.end(), value.begin(), value.end()); + key_buffer_.push_back('\"'); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(value, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.push_back('\"'); + key_buffer_.insert(key_buffer_.end(), value.begin(), value.end()); + key_buffer_.push_back('\"'); + retval = true; + break; + default: + retval = destination_->string_value(value, tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_byte_string(const byte_string_view& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + switch (tag) + { + case semantic_tag::base64: + encode_base64(value.begin(), value.end(), key_); + break; + case semantic_tag::base16: + encode_base16(value.begin(), value.end(),key_); + break; + default: + encode_base64url(value.begin(), value.end(),key_); + break; + } + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.push_back('\"'); + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back('\"'); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.push_back('\"'); + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back('\"'); + retval = true; + break; + default: + retval = destination_->byte_string_value(value, tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_byte_string(const byte_string_view& value, + uint64_t ext_tag, + const ser_context& context, + std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + encode_base64url(value.begin(), value.end(),key_); + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.push_back('\"'); + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back('\"'); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.push_back('\"'); + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back('\"'); + retval = true; + break; + default: + retval = destination_->byte_string_value(value, ext_tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_uint64(uint64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + jsoncons::detail::from_integer(value,key_); + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + retval = true; + break; + default: + retval = destination_->uint64_value(value, tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_int64(int64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + jsoncons::detail::from_integer(value,key_); + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + retval = true; + break; + default: + retval = destination_->int64_value(value, tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_half(uint16_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + jsoncons::string_sink sink(key_); + jsoncons::detail::write_double f{float_chars_format::general,0}; + double x = binary::decode_half(value); + f(x, sink); + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + retval = true; + break; + default: + retval = destination_->half_value(value, tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_double(double value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + string_sink sink(key_); + jsoncons::detail::write_double f{float_chars_format::general,0}; + f(value, sink); + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + retval = true; + break; + default: + retval = destination_->double_value(value, tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + if (value) + { + key_.insert(key_.begin(), true_constant.begin(), true_constant.end()); + } + else + { + key_.insert(key_.begin(), false_constant.begin(), false_constant.end()); + } + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + retval = true; + break; + default: + retval = destination_->bool_value(value, tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_null(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + if (level_stack_.back().is_key() || level_stack_.back().target() == target_t::buffer) + { + key_.clear(); + key_.insert(key_.begin(), null_constant.begin(), null_constant.end()); + } + + bool retval; + if (level_stack_.back().is_key()) + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + key_buffer_.push_back(':'); + retval = true; + break; + default: + retval = destination_->key(key_, context, ec); + break; + } + } + else + { + switch (level_stack_.back().target()) + { + case target_t::buffer: + if (!level_stack_.back().is_object() && level_stack_.back().count() > 0) + { + key_buffer_.push_back(','); + } + key_buffer_.insert(key_buffer_.end(), key_.begin(), key_.end()); + retval = true; + break; + default: + retval = destination_->null_value(tag, context, ec); + break; + } + } + + level_stack_.back().advance(); + return retval; + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(half_arg_t, + const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(half_arg,s,tag,context,ec); + } + else + { + return destination_->typed_array(half_arg, s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + bool is_key = level_stack_.back().is_key(); + level_stack_.back().advance(); + + if (is_key || level_stack_.back().target() == target_t::buffer) + { + return basic_item_event_visitor::visit_typed_array(s,tag,context,ec); + } + else + { + return destination_->typed_array(s, tag, context, ec); + } + } + }; + + template + class basic_default_item_event_visitor : public basic_item_event_visitor + { + bool parse_more_; + std::error_code ec_; + public: + using typename basic_item_event_visitor::string_view_type; + + basic_default_item_event_visitor(bool accept_more = true, + std::error_code ec = std::error_code()) + : parse_more_(accept_more), ec_(ec) + { + } + private: + void visit_flush() override + { + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_begin_object(std::size_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_end_object(const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_begin_array(std::size_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_end_array(const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_null(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_string(const string_view_type&, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_byte_string(const byte_string_view&, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_uint64(uint64_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_int64(int64_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_half(uint16_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_double(double, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_bool(bool, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + }; + + // basic_json_visitor_to_item_event_visitor + + template + class basic_json_visitor_to_item_event_visitor : public basic_json_visitor + { + public: + using typename basic_json_visitor::char_type; + using typename basic_json_visitor::string_view_type; + private: + basic_item_event_visitor& destination_; + + // noncopyable and nonmoveable + basic_json_visitor_to_item_event_visitor(const basic_json_visitor_to_item_event_visitor&) = delete; + basic_json_visitor_to_item_event_visitor& operator=(const basic_json_visitor_to_item_event_visitor&) = delete; + public: + basic_json_visitor_to_item_event_visitor(basic_item_event_visitor& visitor) + : destination_(visitor) + { + } + + basic_item_event_visitor& destination() + { + return destination_; + } + + private: + void visit_flush() override + { + destination_.flush(); + } + + bool visit_begin_object(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.begin_object(tag, context, ec); + } + + bool visit_begin_object(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.begin_object(length, tag, context, ec); + } + + bool visit_end_object(const ser_context& context, std::error_code& ec) override + { + return destination_.end_object(context, ec); + } + + bool visit_begin_array(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.begin_array(tag, context, ec); + } + + bool visit_begin_array(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.begin_array(length, tag, context, ec); + } + + bool visit_end_array(const ser_context& context, std::error_code& ec) override + { + return destination_.end_array(context, ec); + } + + bool visit_key(const string_view_type& name, + const ser_context& context, + std::error_code& ec) override + { + return destination_.visit_string(name, context, ec); + } + + bool visit_string(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_.string_value(value, tag, context, ec); + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_.byte_string_value(b, tag, context, ec); + } + + bool visit_uint64(uint64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.uint64_value(value, tag, context, ec); + } + + bool visit_int64(int64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.int64_value(value, tag, context, ec); + } + + bool visit_half(uint16_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.half_value(value, tag, context, ec); + } + + bool visit_double(double value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.double_value(value, tag, context, ec); + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.bool_value(value, tag, context, ec); + } + + bool visit_null(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_.null_value(tag, context, ec); + } + }; + + class diagnostics_visitor2 : public basic_default_item_event_visitor + { + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_begin_object" << std::endl; + return true; + } + + bool visit_begin_object(std::size_t length, semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_begin_object " << length << std::endl; + return true; + } + + bool visit_end_object(const ser_context&, std::error_code&) override + { + std::cout << "visit_end_object" << std::endl; + return true; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_begin_array" << std::endl; + return true; + } + + bool visit_begin_array(std::size_t length, semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_begin_array " << length << std::endl; + return true; + } + + bool visit_end_array(const ser_context&, std::error_code&) override + { + std::cout << "visit_end_array" << std::endl; + return true; + } + + bool visit_string(const string_view_type& s, semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_string " << s << std::endl; + return true; + } + bool visit_int64(int64_t val, semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_int64 " << val << std::endl; + return true; + } + bool visit_uint64(uint64_t val, semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_uint64 " << val << std::endl; + return true; + } + bool visit_bool(bool val, semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_bool " << val << std::endl; + return true; + } + bool visit_null(semantic_tag, const ser_context&, std::error_code&) override + { + std::cout << "visit_null " << std::endl; + return true; + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + std::cout << "visit_typed_array uint16_t " << tag << std::endl; + for (auto val : s) + { + std::cout << val << "" << std::endl; + } + std::cout << "" << std::endl; + return true; + } + + bool visit_typed_array(half_arg_t, const jsoncons::span& s, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + std::cout << "visit_typed_array half_arg_t uint16_t " << tag << "" << std::endl; + for (auto val : s) + { + std::cout << val << "" << std::endl; + } + std::cout << "" << std::endl; + return true; + } + }; + + using item_event_visitor = basic_item_event_visitor; + using default_item_event_visitor = basic_default_item_event_visitor; + using item_event_visitor_to_visitor_adaptor = basic_item_event_visitor_to_json_visitor; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/json.hpp b/third_party/jsoncons/json.hpp new file mode 100644 index 0000000000..bc3cbe5f9f --- /dev/null +++ b/third_party/jsoncons/json.hpp @@ -0,0 +1,17 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_HPP +#define JSONCONS_JSON_HPP + +#include +#include +#include +#include +#include + +#endif + diff --git a/third_party/jsoncons/json_array.hpp b/third_party/jsoncons/json_array.hpp new file mode 100644 index 0000000000..06cb69464d --- /dev/null +++ b/third_party/jsoncons/json_array.hpp @@ -0,0 +1,283 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_ARRAY_HPP +#define JSONCONS_JSON_ARRAY_HPP + +#include +#include +#include +#include +#include // std::sort, std::stable_sort, std::lower_bound, std::unique +#include +#include +#include // std::iterator_traits +#include // std::allocator +#include // std::move +#include // assert +#include // std::enable_if +#include +#include + +namespace jsoncons { + + // json_array + + template class SequenceContainer = std::vector> + class json_array : public allocator_holder + { + public: + using allocator_type = typename Json::allocator_type; + using value_type = Json; + private: + using value_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using value_container_type = SequenceContainer; + value_container_type elements_; + public: + using iterator = typename value_container_type::iterator; + using const_iterator = typename value_container_type::const_iterator; + using reference = typename std::iterator_traits::reference; + using const_reference = typename std::iterator_traits::reference; + + using allocator_holder::get_allocator; + + json_array() + { + } + + explicit json_array(const allocator_type& alloc) + : allocator_holder(alloc), + elements_(value_allocator_type(alloc)) + { + } + + explicit json_array(std::size_t n, + const allocator_type& alloc = allocator_type()) + : allocator_holder(alloc), + elements_(n,Json(),value_allocator_type(alloc)) + { + } + + explicit json_array(std::size_t n, + const Json& value, + const allocator_type& alloc = allocator_type()) + : allocator_holder(alloc), + elements_(n,value,value_allocator_type(alloc)) + { + } + + template + json_array(InputIterator begin, InputIterator end, const allocator_type& alloc = allocator_type()) + : allocator_holder(alloc), + elements_(begin,end,value_allocator_type(alloc)) + { + } + + json_array(const json_array& other) + : allocator_holder(other.get_allocator()), + elements_(other.elements_) + { + } + json_array(const json_array& other, const allocator_type& alloc) + : allocator_holder(alloc), + elements_(other.elements_,value_allocator_type(alloc)) + { + } + + json_array(json_array&& other) noexcept + : allocator_holder(other.get_allocator()), + elements_(std::move(other.elements_)) + { + } + json_array(json_array&& other, const allocator_type& alloc) + : allocator_holder(alloc), + elements_(std::move(other.elements_),value_allocator_type(alloc)) + { + } + + json_array(const std::initializer_list& init, + const allocator_type& alloc = allocator_type()) + : allocator_holder(alloc), + elements_(init,value_allocator_type(alloc)) + { + } + ~json_array() noexcept + { + flatten_and_destroy(); + } + + reference back() + { + return elements_.back(); + } + + const_reference back() const + { + return elements_.back(); + } + + void pop_back() + { + elements_.pop_back(); + } + + bool empty() const + { + return elements_.empty(); + } + + void swap(json_array& other) noexcept + { + elements_.swap(other.elements_); + } + + std::size_t size() const {return elements_.size();} + + std::size_t capacity() const {return elements_.capacity();} + + void clear() {elements_.clear();} + + void shrink_to_fit() + { + for (std::size_t i = 0; i < elements_.size(); ++i) + { + elements_[i].shrink_to_fit(); + } + elements_.shrink_to_fit(); + } + + void reserve(std::size_t n) {elements_.reserve(n);} + + void resize(std::size_t n) {elements_.resize(n);} + + void resize(std::size_t n, const Json& val) {elements_.resize(n,val);} + + iterator erase(const_iterator pos) + { + return elements_.erase(pos); + } + + iterator erase(const_iterator first, const_iterator last) + { + return elements_.erase(first,last); + } + + Json& operator[](std::size_t i) {return elements_[i];} + + const Json& operator[](std::size_t i) const {return elements_[i];} + + // push_back + + template + typename std::enable_if::value,void>::type + push_back(T&& value) + { + elements_.emplace_back(std::forward(value)); + } + + template + typename std::enable_if::value,void>::type + push_back(T&& value) + { + elements_.emplace_back(std::forward(value)); + } + + template + typename std::enable_if::value,iterator>::type + insert(const_iterator pos, T&& value) + { + return elements_.emplace(pos, std::forward(value)); + } + template + typename std::enable_if::value,iterator>::type + insert(const_iterator pos, T&& value) + { + return elements_.emplace(pos, std::forward(value)); + } + + template + iterator insert(const_iterator pos, InputIt first, InputIt last) + { + return elements_.insert(pos, first, last); + } + + template + typename std::enable_if::value,iterator>::type + emplace(const_iterator pos, Args&&... args) + { + return elements_.emplace(pos, std::forward(args)...); + } + + template + Json& emplace_back(Args&&... args) + { + elements_.emplace_back(std::forward(args)...); + return elements_.back(); + } + + iterator begin() {return elements_.begin();} + + iterator end() {return elements_.end();} + + const_iterator begin() const {return elements_.begin();} + + const_iterator end() const {return elements_.end();} + + bool operator==(const json_array& rhs) const noexcept + { + return elements_ == rhs.elements_; + } + + bool operator<(const json_array& rhs) const noexcept + { + return elements_ < rhs.elements_; + } + private: + + json_array& operator=(const json_array&) = delete; + + void flatten_and_destroy() noexcept + { + while (!elements_.empty()) + { + value_type current = std::move(elements_.back()); + elements_.pop_back(); + switch (current.storage_kind()) + { + case json_storage_kind::array: + { + for (auto&& item : current.array_range()) + { + if (item.size() > 0) // non-empty object or array + { + elements_.push_back(std::move(item)); + } + } + current.clear(); + break; + } + case json_storage_kind::object: + { + for (auto&& kv : current.object_range()) + { + if (kv.value().size() > 0) // non-empty object or array + { + elements_.push_back(std::move(kv.value())); + } + } + current.clear(); + break; + } + default: + break; + } + } + } + }; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/json_cursor.hpp b/third_party/jsoncons/json_cursor.hpp new file mode 100644 index 0000000000..2b09d71711 --- /dev/null +++ b/third_party/jsoncons/json_cursor.hpp @@ -0,0 +1,441 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_CURSOR_HPP +#define JSONCONS_JSON_CURSOR_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + +template ,typename Allocator=std::allocator> +class basic_json_cursor : public basic_staj_cursor, private virtual ser_context +{ +public: + using source_type = Source; + using char_type = CharT; + using allocator_type = Allocator; + using string_view_type = jsoncons::basic_string_view; +private: + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + static constexpr size_t default_max_buffer_size = 16384; + + json_source_adaptor source_; + basic_json_parser parser_; + basic_staj_visitor cursor_visitor_; + bool done_; + + // Noncopyable and nonmoveable + basic_json_cursor(const basic_json_cursor&) = delete; + basic_json_cursor& operator=(const basic_json_cursor&) = delete; + +public: + + // Constructors that throw parse exceptions + + template + basic_json_cursor(Sourceable&& source, + const basic_json_decode_options& options = basic_json_decode_options(), + std::function err_handler = default_json_parsing(), + const Allocator& alloc = Allocator(), + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(std::forward(source)), + parser_(options,err_handler,alloc), + cursor_visitor_(accept_all), + done_(false) + { + if (!done()) + { + next(); + } + } + + template + basic_json_cursor(Sourceable&& source, + const basic_json_decode_options& options = basic_json_decode_options(), + std::function err_handler = default_json_parsing(), + const Allocator& alloc = Allocator(), + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(), + parser_(options, err_handler, alloc), + cursor_visitor_(accept_all), + done_(false) + { + initialize_with_string_view(std::forward(source)); + } + + + // Constructors that set parse error codes + template + basic_json_cursor(Sourceable&& source, + std::error_code& ec) + : basic_json_cursor(std::allocator_arg, Allocator(), + std::forward(source), + basic_json_decode_options(), + default_json_parsing(), + ec) + { + } + + template + basic_json_cursor(Sourceable&& source, + const basic_json_decode_options& options, + std::error_code& ec) + : basic_json_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + default_json_parsing(), + ec) + { + } + + template + basic_json_cursor(Sourceable&& source, + const basic_json_decode_options& options, + std::function err_handler, + std::error_code& ec) + : basic_json_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + err_handler, + ec) + { + } + + template + basic_json_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const basic_json_decode_options& options, + std::function err_handler, + std::error_code& ec, + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(std::forward(source)), + parser_(options,err_handler,alloc), + cursor_visitor_(accept_all), + done_(false) + { + if (!done()) + { + next(ec); + } + } + + template + basic_json_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const basic_json_decode_options& options, + std::function err_handler, + std::error_code& ec, + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(), + parser_(options, err_handler, alloc), + cursor_visitor_(accept_all), + done_(false) + { + initialize_with_string_view(std::forward(source), ec); + } + + void reset() + { + parser_.reset(); + cursor_visitor_.reset(); + done_ = false; + if (!done()) + { + next(); + } + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source) + { + source_ = std::forward(source); + parser_.reinitialize(); + cursor_visitor_.reset(); + done_ = false; + if (!done()) + { + next(); + } + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source) + { + source_ = {}; + parser_.reinitialize(); + cursor_visitor_.reset(); + done_ = false; + initialize_with_string_view(std::forward(source)); + } + + void reset(std::error_code& ec) + { + parser_.reset(); + cursor_visitor_.reset(); + done_ = false; + if (!done()) + { + next(ec); + } + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source, std::error_code& ec) + { + source_ = std::forward(source); + parser_.reinitialize(); + cursor_visitor_.reset(); + done_ = false; + if (!done()) + { + next(ec); + } + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source, std::error_code& ec) + { + source_ = {}; + parser_.reinitialize(); + done_ = false; + initialize_with_string_view(std::forward(source), ec); + } + + bool done() const override + { + return parser_.done() || done_; + } + + const basic_staj_event& current() const override + { + return cursor_visitor_.event(); + } + + void read_to(basic_json_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_json_visitor& visitor, + std::error_code& ec) override + { + if (cursor_visitor_.event().send_json_event(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + void check_done() + { + std::error_code ec; + check_done(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + const ser_context& context() const override + { + return *this; + } + + void check_done(std::error_code& ec) + { + if (source_.is_error()) + { + ec = json_errc::source_error; + return; + } + if (source_.eof()) + { + parser_.check_done(ec); + if (ec) return; + } + else + { + do + { + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + } + } + if (!parser_.source_exhausted()) + { + parser_.check_done(ec); + if (ec) return; + } + } + while (!eof()); + } + } + + bool eof() const + { + return parser_.source_exhausted() && source_.eof(); + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + basic_staj_filter_view operator|(basic_json_cursor& cursor, + std::function&, const ser_context&)> pred) + { + return basic_staj_filter_view(cursor, pred); + } + +private: + + static bool accept_all(const basic_staj_event&, const ser_context&) + { + return true; + } + + void initialize_with_string_view(string_view_type sv) + { + auto r = unicode_traits::detect_json_encoding(sv.data(), sv.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + JSONCONS_THROW(ser_error(json_errc::illegal_unicode_character,parser_.line(),parser_.column())); + } + std::size_t offset = (r.ptr - sv.data()); + parser_.update(sv.data()+offset,sv.size()-offset); + + bool is_done = parser_.done() || done_; + if (!is_done) + { + read_next(); + } + } + + void initialize_with_string_view(string_view_type sv, std::error_code& ec) + { + auto r = unicode_traits::detect_json_encoding(sv.data(), sv.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + ec = json_errc::illegal_unicode_character; + return; + } + std::size_t offset = (r.ptr - sv.data()); + parser_.update(sv.data()+offset,sv.size()-offset); + bool is_done = parser_.done() || done_; + if (!is_done) + { + read_next(ec); + } + } + + void read_next() + { + std::error_code ec; + read_next(cursor_visitor_, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_next(std::error_code& ec) + { + read_next(cursor_visitor_, ec); + } + + void read_next(basic_json_visitor& visitor, std::error_code& ec) + { + parser_.restart(); + while (!parser_.stopped()) + { + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + if (ec) return; + } + } + bool eof = parser_.source_exhausted() && source_.eof(); + parser_.parse_some(visitor, ec); + if (ec) return; + if (eof) + { + if (parser_.enter()) + { + done_ = true; + break; + } + else if (!parser_.accept()) + { + ec = json_errc::unexpected_eof; + return; + } + } + } + } +}; + +using json_stream_cursor = basic_json_cursor>; +using json_string_cursor = basic_json_cursor>; +using wjson_stream_cursor = basic_json_cursor>; +using wjson_string_cursor = basic_json_cursor>; + +} + +#endif + diff --git a/third_party/jsoncons/json_decoder.hpp b/third_party/jsoncons/json_decoder.hpp new file mode 100644 index 0000000000..7b7847145a --- /dev/null +++ b/third_party/jsoncons/json_decoder.hpp @@ -0,0 +1,384 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_DECODER_HPP +#define JSONCONS_JSON_DECODER_HPP + +#include +#include +#include // std::true_type +#include // std::allocator +#include // std::make_move_iterator +#include // std::move +#include +#include + +namespace jsoncons { + +template > +class json_decoder final : public basic_json_visitor +{ +public: + using char_type = typename Json::char_type; + using typename basic_json_visitor::string_view_type; + + using key_value_type = typename Json::key_value_type; + using key_type = typename Json::key_type; + using array = typename Json::array; + using object = typename Json::object; + using allocator_type = typename Json::allocator_type; + using json_string_allocator = typename key_type::allocator_type; + using json_array_allocator = typename array::allocator_type; + using json_object_allocator = typename object::allocator_type; + using json_byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; +private: + + enum class structure_type {root_t, array_t, object_t}; + + struct structure_info + { + structure_type type_; + std::size_t container_index_; + + structure_info(structure_type type, std::size_t offset) noexcept + : type_(type), container_index_(offset) + { + } + + }; + + using temp_allocator_type = TempAllocator; + using stack_item_allocator_type = typename std::allocator_traits:: template rebind_alloc>; + using structure_info_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + allocator_type allocator_; + + Json result_; + + std::size_t index_; + key_type name_; + std::vector,stack_item_allocator_type> item_stack_; + std::vector structure_stack_; + bool is_valid_; + +public: + json_decoder(const allocator_type& alloc = allocator_type(), + const temp_allocator_type& temp_alloc = temp_allocator_type()) + : allocator_(alloc), + result_(), + index_(0), + name_(alloc), + item_stack_(alloc), + structure_stack_(temp_alloc), + is_valid_(false) + { + item_stack_.reserve(1000); + structure_stack_.reserve(100); + structure_stack_.emplace_back(structure_type::root_t, 0); + } + + json_decoder(temp_allocator_arg_t, + const temp_allocator_type& temp_alloc = temp_allocator_type()) + : allocator_(), + result_(), + index_(0), + name_(), + item_stack_(), + structure_stack_(temp_alloc), + is_valid_(false) + { + item_stack_.reserve(1000); + structure_stack_.reserve(100); + structure_stack_.emplace_back(structure_type::root_t, 0); + } + + void reset() + { + is_valid_ = false; + index_ = 0; + item_stack_.clear(); + structure_stack_.clear(); + structure_stack_.emplace_back(structure_type::root_t, 0); + } + + bool is_valid() const + { + return is_valid_; + } + + Json get_result() + { + JSONCONS_ASSERT(is_valid_); + is_valid_ = false; + return std::move(result_); + } + +private: + + void visit_flush() override + { + } + + bool visit_begin_object(semantic_tag tag, const ser_context&, std::error_code&) override + { + if (structure_stack_.back().type_ == structure_type::root_t) + { + index_ = 0; + item_stack_.clear(); + is_valid_ = false; + } + item_stack_.emplace_back(std::move(name_), index_++, json_object_arg, tag); + structure_stack_.emplace_back(structure_type::object_t, item_stack_.size()-1); + return true; + } + + bool visit_end_object(const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(structure_stack_.size() > 0); + JSONCONS_ASSERT(structure_stack_.back().type_ == structure_type::object_t); + const size_t structure_index = structure_stack_.back().container_index_; + JSONCONS_ASSERT(item_stack_.size() > structure_index); + const size_t count = item_stack_.size() - (structure_index + 1); + auto first = item_stack_.begin() + (structure_index+1); + + if (count > 0) + { + item_stack_[structure_index].value.template cast().value().uninitialized_init( + &item_stack_[structure_index+1], count); + } + + item_stack_.erase(first, item_stack_.end()); + structure_stack_.pop_back(); + if (structure_stack_.back().type_ == structure_type::root_t) + { + result_.swap(item_stack_.front().value); + item_stack_.pop_back(); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_begin_array(semantic_tag tag, const ser_context&, std::error_code&) override + { + if (structure_stack_.back().type_ == structure_type::root_t) + { + index_ = 0; + item_stack_.clear(); + is_valid_ = false; + } + item_stack_.emplace_back(std::move(name_), index_++, json_array_arg, tag); + structure_stack_.emplace_back(structure_type::array_t, item_stack_.size()-1); + return true; + } + + bool visit_end_array(const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(structure_stack_.size() > 1); + JSONCONS_ASSERT(structure_stack_.back().type_ == structure_type::array_t); + const size_t container_index = structure_stack_.back().container_index_; + JSONCONS_ASSERT(item_stack_.size() > container_index); + + auto& container = item_stack_[container_index].value; + + const size_t size = item_stack_.size() - (container_index + 1); + //std::cout << "size on item stack: " << size << "\n"; + + if (size > 0) + { + container.reserve(size); + auto first = item_stack_.begin() + (container_index+1); + auto last = first + size; + for (auto it = first; it != last; ++it) + { + container.push_back(std::move(it->value)); + } + item_stack_.erase(first, item_stack_.end()); + } + + structure_stack_.pop_back(); + if (structure_stack_.back().type_ == structure_type::root_t) + { + result_.swap(item_stack_.front().value); + item_stack_.pop_back(); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) override + { + name_ = key_type(name.data(),name.length(),allocator_); + return true; + } + + bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, sv, tag); + break; + case structure_type::root_t: + result_ = Json(sv, tag, allocator_); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, byte_string_arg, b, tag); + break; + case structure_type::root_t: + result_ = Json(byte_string_arg, b, tag, allocator_); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_byte_string(const byte_string_view& b, + uint64_t ext_tag, + const ser_context&, + std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, byte_string_arg, b, ext_tag); + break; + case structure_type::root_t: + result_ = Json(byte_string_arg, b, ext_tag, allocator_); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, value, tag); + break; + case structure_type::root_t: + result_ = Json(value,tag); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, value, tag); + break; + case structure_type::root_t: + result_ = Json(value,tag); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_half(uint16_t value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, half_arg, value, tag); + break; + case structure_type::root_t: + result_ = Json(half_arg, value, tag); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_double(double value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, value, tag); + break; + case structure_type::root_t: + result_ = Json(value, tag); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context&, std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, value, tag); + break; + case structure_type::root_t: + result_ = Json(value, tag); + is_valid_ = true; + return false; + } + return true; + } + + bool visit_null(semantic_tag tag, const ser_context&, std::error_code&) override + { + switch (structure_stack_.back().type_) + { + case structure_type::object_t: + case structure_type::array_t: + item_stack_.emplace_back(std::move(name_), index_++, null_type(), tag); + break; + case structure_type::root_t: + result_ = Json(null_type(), tag); + is_valid_ = true; + return false; + } + return true; + } +}; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/json_encoder.hpp b/third_party/jsoncons/json_encoder.hpp new file mode 100644 index 0000000000..4752f36edd --- /dev/null +++ b/third_party/jsoncons/json_encoder.hpp @@ -0,0 +1,1604 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_ENCODER_HPP +#define JSONCONS_JSON_ENCODER_HPP + +#include // std::array +#include +#include +#include // std::isfinite, std::isnan +#include // std::numeric_limits +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace detail { + + inline + bool is_control_character(uint32_t c) + { + return c <= 0x1F || c == 0x7f; + } + + inline + bool is_non_ascii_codepoint(uint32_t cp) + { + return cp >= 0x80; + } + + template + std::size_t escape_string(const CharT* s, std::size_t length, + bool escape_all_non_ascii, bool escape_solidus, + Sink& sink) + { + std::size_t count = 0; + const CharT* begin = s; + const CharT* end = s + length; + for (const CharT* it = begin; it != end; ++it) + { + CharT c = *it; + switch (c) + { + case '\\': + sink.push_back('\\'); + sink.push_back('\\'); + count += 2; + break; + case '"': + sink.push_back('\\'); + sink.push_back('\"'); + count += 2; + break; + case '\b': + sink.push_back('\\'); + sink.push_back('b'); + count += 2; + break; + case '\f': + sink.push_back('\\'); + sink.push_back('f'); + count += 2; + break; + case '\n': + sink.push_back('\\'); + sink.push_back('n'); + count += 2; + break; + case '\r': + sink.push_back('\\'); + sink.push_back('r'); + count += 2; + break; + case '\t': + sink.push_back('\\'); + sink.push_back('t'); + count += 2; + break; + default: + if (escape_solidus && c == '/') + { + sink.push_back('\\'); + sink.push_back('/'); + count += 2; + } + else if (is_control_character(c) || escape_all_non_ascii) + { + // convert to codepoint + uint32_t cp; + auto r = unicode_traits::to_codepoint(it, end, cp, unicode_traits::conv_flags::strict); + if (r.ec != unicode_traits::conv_errc()) + { + JSONCONS_THROW(ser_error(json_errc::illegal_codepoint)); + } + it = r.ptr - 1; + if (is_non_ascii_codepoint(cp) || is_control_character(c)) + { + if (cp > 0xFFFF) + { + cp -= 0x10000; + uint32_t first = (cp >> 10) + 0xD800; + uint32_t second = ((cp & 0x03FF) + 0xDC00); + + sink.push_back('\\'); + sink.push_back('u'); + sink.push_back(jsoncons::detail::to_hex_character(first >> 12 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(first >> 8 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(first >> 4 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(first & 0x000F)); + sink.push_back('\\'); + sink.push_back('u'); + sink.push_back(jsoncons::detail::to_hex_character(second >> 12 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(second >> 8 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(second >> 4 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(second & 0x000F)); + count += 12; + } + else + { + sink.push_back('\\'); + sink.push_back('u'); + sink.push_back(jsoncons::detail::to_hex_character(cp >> 12 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(cp >> 8 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(cp >> 4 & 0x000F)); + sink.push_back(jsoncons::detail::to_hex_character(cp & 0x000F)); + count += 6; + } + } + else + { + sink.push_back(c); + ++count; + } + } + else + { + sink.push_back(c); + ++count; + } + break; + } + } + return count; + } + + inline + byte_string_chars_format resolve_byte_string_chars_format(byte_string_chars_format format1, + byte_string_chars_format format2, + byte_string_chars_format default_format = byte_string_chars_format::base64url) + { + byte_string_chars_format sink; + switch (format1) + { + case byte_string_chars_format::base16: + case byte_string_chars_format::base64: + case byte_string_chars_format::base64url: + sink = format1; + break; + default: + switch (format2) + { + case byte_string_chars_format::base64url: + case byte_string_chars_format::base64: + case byte_string_chars_format::base16: + sink = format2; + break; + default: // base64url + { + sink = default_format; + break; + } + } + break; + } + return sink; + } + +} // namespace detail + + template ,typename Allocator=std::allocator> + class basic_json_encoder final : public basic_json_visitor + { + static const jsoncons::basic_string_view null_constant() + { + static const jsoncons::basic_string_view k = JSONCONS_STRING_VIEW_CONSTANT(CharT, "null"); + return k; + } + static const jsoncons::basic_string_view true_constant() + { + static const jsoncons::basic_string_view k = JSONCONS_STRING_VIEW_CONSTANT(CharT, "true"); + return k; + } + static const jsoncons::basic_string_view false_constant() + { + static const jsoncons::basic_string_view k = JSONCONS_STRING_VIEW_CONSTANT(CharT, "false"); + return k; + } + public: + using allocator_type = Allocator; + using char_type = CharT; + using typename basic_json_visitor::string_view_type; + using sink_type = Sink; + using string_type = typename basic_json_encode_options::string_type; + + private: + enum class container_type {object, array}; + + class encoding_context + { + container_type type_; + std::size_t count_; + line_split_kind line_splits_; + bool indent_before_; + bool new_line_after_; + std::size_t begin_pos_; + std::size_t data_pos_; + public: + encoding_context(container_type type, line_split_kind split_lines, bool indent_once, + std::size_t begin_pos, std::size_t data_pos) noexcept + : type_(type), count_(0), line_splits_(split_lines), indent_before_(indent_once), new_line_after_(false), + begin_pos_(begin_pos), data_pos_(data_pos) + { + } + + encoding_context(const encoding_context&) = default; + encoding_context& operator=(const encoding_context&) = default; + + void set_position(std::size_t pos) + { + data_pos_ = pos; + } + + std::size_t begin_pos() const + { + return begin_pos_; + } + + std::size_t data_pos() const + { + return data_pos_; + } + + std::size_t count() const + { + return count_; + } + + void increment_count() + { + ++count_; + } + + bool new_line_after() const + { + return new_line_after_; + } + + void new_line_after(bool value) + { + new_line_after_ = value; + } + + bool is_object() const + { + return type_ == container_type::object; + } + + bool is_array() const + { + return type_ == container_type::array; + } + + bool is_same_line() const + { + return line_splits_ == line_split_kind::same_line; + } + + bool is_new_line() const + { + return line_splits_ == line_split_kind::new_line; + } + + bool is_multi_line() const + { + return line_splits_ == line_split_kind::multi_line; + } + + bool is_indent_once() const + { + return count_ == 0 ? indent_before_ : false; + } + + }; + using encoding_context_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + Sink sink_; + basic_json_encode_options options_; + jsoncons::detail::write_double fp_; + + std::vector stack_; + int indent_amount_; + std::size_t column_; + std::basic_string colon_str_; + std::basic_string comma_str_; + std::basic_string open_object_brace_str_; + std::basic_string close_object_brace_str_; + std::basic_string open_array_bracket_str_; + std::basic_string close_array_bracket_str_; + int nesting_depth_; + + // Noncopyable and nonmoveable + basic_json_encoder(const basic_json_encoder&) = delete; + basic_json_encoder& operator=(const basic_json_encoder&) = delete; + public: + basic_json_encoder(Sink&& sink, + const Allocator& alloc = Allocator()) + : basic_json_encoder(std::forward(sink), basic_json_encode_options(), alloc) + { + } + + basic_json_encoder(Sink&& sink, + const basic_json_encode_options& options, + const Allocator& alloc = Allocator()) + : sink_(std::forward(sink)), + options_(options), + fp_(options.float_format(), options.precision()), + stack_(alloc), + indent_amount_(0), + column_(0), + nesting_depth_(0) + { + switch (options.spaces_around_colon()) + { + case spaces_option::space_after: + colon_str_ = std::basic_string({':',' '}); + break; + case spaces_option::space_before: + colon_str_ = std::basic_string({' ',':'}); + break; + case spaces_option::space_before_and_after: + colon_str_ = std::basic_string({' ',':',' '}); + break; + default: + colon_str_.push_back(':'); + break; + } + switch (options.spaces_around_comma()) + { + case spaces_option::space_after: + comma_str_ = std::basic_string({',',' '}); + break; + case spaces_option::space_before: + comma_str_ = std::basic_string({' ',','}); + break; + case spaces_option::space_before_and_after: + comma_str_ = std::basic_string({' ',',',' '}); + break; + default: + comma_str_.push_back(','); + break; + } + if (options.pad_inside_object_braces()) + { + open_object_brace_str_ = std::basic_string({'{', ' '}); + close_object_brace_str_ = std::basic_string({' ', '}'}); + } + else + { + open_object_brace_str_.push_back('{'); + close_object_brace_str_.push_back('}'); + } + if (options.pad_inside_array_brackets()) + { + open_array_bracket_str_ = std::basic_string({'[', ' '}); + close_array_bracket_str_ = std::basic_string({' ', ']'}); + } + else + { + open_array_bracket_str_.push_back('['); + close_array_bracket_str_.push_back(']'); + } + } + + ~basic_json_encoder() noexcept + { + JSONCONS_TRY + { + sink_.flush(); + } + JSONCONS_CATCH(...) + { + } + } + + void reset() + { + stack_.clear(); + indent_amount_ = 0; + column_ = 0; + nesting_depth_ = 0; + } + + void reset(Sink&& sink) + { + sink_ = std::move(sink); + reset(); + } + + private: + // Implementing methods + void visit_flush() final + { + sink_.flush(); + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) final + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = json_errc::max_nesting_depth_exceeded; + return false; + } + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.append(comma_str_.data(),comma_str_.length()); + column_ += comma_str_.length(); + } + + if (!stack_.empty()) // object or array + { + if (stack_.back().is_object()) + { + switch (options_.object_object_line_splits()) + { + case line_split_kind::same_line: + case line_split_kind::new_line: + if (column_ >= options_.line_length_limit()) + { + break_line(); + } + break; + default: // multi_line + break; + } + stack_.emplace_back(container_type::object,options_.object_object_line_splits(), false, + column_, column_+open_object_brace_str_.length()); + } + else // array + { + switch (options_.array_object_line_splits()) + { + case line_split_kind::same_line: + if (column_ >= options_.line_length_limit()) + { + //stack_.back().new_line_after(true); + new_line(); + } + break; + case line_split_kind::new_line: + stack_.back().new_line_after(true); + new_line(); + break; + default: // multi_line + stack_.back().new_line_after(true); + new_line(); + break; + } + stack_.emplace_back(container_type::object,options_.array_object_line_splits(), false, + column_, column_+open_object_brace_str_.length()); + } + } + else + { + stack_.emplace_back(container_type::object, options_.line_splits(), false, + column_, column_+open_object_brace_str_.length()); + } + indent(); + + sink_.append(open_object_brace_str_.data(), open_object_brace_str_.length()); + column_ += open_object_brace_str_.length(); + return true; + } + + bool visit_end_object(const ser_context&, std::error_code&) final + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + unindent(); + if (stack_.back().new_line_after()) + { + new_line(); + } + stack_.pop_back(); + sink_.append(close_object_brace_str_.data(), close_object_brace_str_.length()); + column_ += close_object_brace_str_.length(); + + end_value(); + return true; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) final + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = json_errc::max_nesting_depth_exceeded; + return false; + } + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.append(comma_str_.data(),comma_str_.length()); + column_ += comma_str_.length(); + } + if (!stack_.empty()) + { + if (stack_.back().is_object()) + { + switch (options_.object_array_line_splits()) + { + case line_split_kind::same_line: + stack_.emplace_back(container_type::array,options_.object_array_line_splits(),false, + column_, column_ + open_array_bracket_str_.length()); + break; + case line_split_kind::new_line: + { + stack_.emplace_back(container_type::array,options_.object_array_line_splits(),true, + column_, column_+open_array_bracket_str_.length()); + break; + } + default: // multi_line + stack_.emplace_back(container_type::array,options_.object_array_line_splits(),true, + column_, column_+open_array_bracket_str_.length()); + break; + } + } + else // array + { + switch (options_.array_array_line_splits()) + { + case line_split_kind::same_line: + if (stack_.back().is_multi_line()) + { + stack_.back().new_line_after(true); + new_line(); + } + stack_.emplace_back(container_type::array,options_.array_array_line_splits(), false, + column_, column_+open_array_bracket_str_.length()); + break; + case line_split_kind::new_line: + stack_.back().new_line_after(true); + new_line(); + stack_.emplace_back(container_type::array,options_.array_array_line_splits(), false, + column_, column_+open_array_bracket_str_.length()); + break; + default: // multi_line + stack_.back().new_line_after(true); + new_line(); + stack_.emplace_back(container_type::array,options_.array_array_line_splits(), false, + column_, column_+open_array_bracket_str_.length()); + //new_line(); + break; + } + } + } + else + { + stack_.emplace_back(container_type::array, options_.line_splits(), false, + column_, column_+open_array_bracket_str_.length()); + } + indent(); + sink_.append(open_array_bracket_str_.data(), open_array_bracket_str_.length()); + column_ += open_array_bracket_str_.length(); + return true; + } + + bool visit_end_array(const ser_context&, std::error_code&) final + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + unindent(); + if (stack_.back().new_line_after()) + { + new_line(); + } + stack_.pop_back(); + sink_.append(close_array_bracket_str_.data(), close_array_bracket_str_.length()); + column_ += close_array_bracket_str_.length(); + end_value(); + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) final + { + JSONCONS_ASSERT(!stack_.empty()); + if (stack_.back().count() > 0) + { + sink_.append(comma_str_.data(),comma_str_.length()); + column_ += comma_str_.length(); + } + + if (stack_.back().is_multi_line()) + { + stack_.back().new_line_after(true); + new_line(); + } + else if (stack_.back().count() > 0 && column_ >= options_.line_length_limit()) + { + //stack_.back().new_line_after(true); + new_line(stack_.back().data_pos()); + } + + if (stack_.back().count() == 0) + { + stack_.back().set_position(column_); + } + sink_.push_back('\"'); + std::size_t length = jsoncons::detail::escape_string(name.data(), name.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_); + sink_.push_back('\"'); + sink_.append(colon_str_.data(),colon_str_.length()); + column_ += (length+2+colon_str_.length()); + return true; + } + + bool visit_null(semantic_tag, const ser_context&, std::error_code&) final + { + if (!stack_.empty()) + { + if (stack_.back().is_array()) + { + begin_scalar_value(); + } + if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit()) + { + break_line(); + } + } + + sink_.append(null_constant().data(), null_constant().size()); + column_ += null_constant().size(); + + end_value(); + return true; + } + + bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context& context, std::error_code& ec) final + { + if (!stack_.empty()) + { + if (stack_.back().is_array()) + { + begin_scalar_value(); + } + if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit()) + { + break_line(); + } + } + + write_string(sv, tag, context, ec); + + end_value(); + return true; + } + + bool write_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code&) + { + switch (tag) + { + case semantic_tag::bigint: + write_bigint_value(sv); + break; + case semantic_tag::bigdec: + { + // output lossless number + if (options_.bigint_format() == bigint_chars_format::number) + { + write_bigint_value(sv); + break; + } + JSONCONS_FALLTHROUGH; + } + default: + { + sink_.push_back('\"'); + std::size_t length = jsoncons::detail::escape_string(sv.data(), sv.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_); + sink_.push_back('\"'); + column_ += (length+2); + break; + } + } + + return true; + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context&, + std::error_code&) final + { + if (!stack_.empty()) + { + if (stack_.back().is_array()) + { + begin_scalar_value(); + } + if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit()) + { + break_line(); + } + } + + byte_string_chars_format encoding_hint; + switch (tag) + { + case semantic_tag::base16: + encoding_hint = byte_string_chars_format::base16; + break; + case semantic_tag::base64: + encoding_hint = byte_string_chars_format::base64; + break; + case semantic_tag::base64url: + encoding_hint = byte_string_chars_format::base64url; + break; + default: + encoding_hint = byte_string_chars_format::none; + break; + } + + byte_string_chars_format format = jsoncons::detail::resolve_byte_string_chars_format(options_.byte_string_format(), + encoding_hint, + byte_string_chars_format::base64url); + switch (format) + { + case byte_string_chars_format::base16: + { + sink_.push_back('\"'); + std::size_t length = encode_base16(b.begin(),b.end(),sink_); + sink_.push_back('\"'); + column_ += (length + 2); + break; + } + case byte_string_chars_format::base64: + { + sink_.push_back('\"'); + std::size_t length = encode_base64(b.begin(), b.end(), sink_); + sink_.push_back('\"'); + column_ += (length + 2); + break; + } + case byte_string_chars_format::base64url: + { + sink_.push_back('\"'); + std::size_t length = encode_base64url(b.begin(),b.end(),sink_); + sink_.push_back('\"'); + column_ += (length + 2); + break; + } + default: + { + JSONCONS_UNREACHABLE(); + } + } + + end_value(); + return true; + } + + bool visit_double(double value, + semantic_tag, + const ser_context& context, + std::error_code& ec) final + { + if (!stack_.empty()) + { + if (stack_.back().is_array()) + { + begin_scalar_value(); + } + if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit()) + { + break_line(); + } + } + + if (!std::isfinite(value)) + { + if ((std::isnan)(value)) + { + if (options_.enable_nan_to_num()) + { + sink_.append(options_.nan_to_num().data(), options_.nan_to_num().length()); + column_ += options_.nan_to_num().length(); + } + else if (options_.enable_nan_to_str()) + { + write_string(options_.nan_to_str(), semantic_tag::none, context, ec); + } + else + { + sink_.append(null_constant().data(), null_constant().size()); + column_ += null_constant().size(); + } + } + else if (value == std::numeric_limits::infinity()) + { + if (options_.enable_inf_to_num()) + { + sink_.append(options_.inf_to_num().data(), options_.inf_to_num().length()); + column_ += options_.inf_to_num().length(); + } + else if (options_.enable_inf_to_str()) + { + write_string(options_.inf_to_str(), semantic_tag::none, context, ec); + } + else + { + sink_.append(null_constant().data(), null_constant().size()); + column_ += null_constant().size(); + } + } + else + { + if (options_.enable_neginf_to_num()) + { + sink_.append(options_.neginf_to_num().data(), options_.neginf_to_num().length()); + column_ += options_.neginf_to_num().length(); + } + else if (options_.enable_neginf_to_str()) + { + write_string(options_.neginf_to_str(), semantic_tag::none, context, ec); + } + else + { + sink_.append(null_constant().data(), null_constant().size()); + column_ += null_constant().size(); + } + } + } + else + { + std::size_t length = fp_(value, sink_); + column_ += length; + } + + end_value(); + return true; + } + + bool visit_int64(int64_t value, + semantic_tag, + const ser_context&, + std::error_code&) final + { + if (!stack_.empty()) + { + if (stack_.back().is_array()) + { + begin_scalar_value(); + } + if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit()) + { + break_line(); + } + } + std::size_t length = jsoncons::detail::from_integer(value, sink_); + column_ += length; + end_value(); + return true; + } + + bool visit_uint64(uint64_t value, + semantic_tag, + const ser_context&, + std::error_code&) final + { + if (!stack_.empty()) + { + if (stack_.back().is_array()) + { + begin_scalar_value(); + } + if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit()) + { + break_line(); + } + } + std::size_t length = jsoncons::detail::from_integer(value, sink_); + column_ += length; + end_value(); + return true; + } + + bool visit_bool(bool value, semantic_tag, const ser_context&, std::error_code&) final + { + if (!stack_.empty()) + { + if (stack_.back().is_array()) + { + begin_scalar_value(); + } + if (!stack_.back().is_multi_line() && column_ >= options_.line_length_limit()) + { + break_line(); + } + } + + if (value) + { + sink_.append(true_constant().data(), true_constant().size()); + column_ += true_constant().size(); + } + else + { + sink_.append(false_constant().data(), false_constant().size()); + column_ += false_constant().size(); + } + + end_value(); + return true; + } + + void begin_scalar_value() + { + if (!stack_.empty()) + { + if (stack_.back().count() > 0) + { + sink_.append(comma_str_.data(),comma_str_.length()); + column_ += comma_str_.length(); + } + if (stack_.back().is_multi_line() || stack_.back().is_indent_once()) + { + stack_.back().new_line_after(true); + new_line(); + } + } + } + + void write_bigint_value(const string_view_type& sv) + { + switch (options_.bigint_format()) + { + case bigint_chars_format::number: + { + sink_.append(sv.data(),sv.size()); + column_ += sv.size(); + break; + } + case bigint_chars_format::base64: + { + bigint n = bigint::from_string(sv.data(), sv.length()); + bool is_neg = n < 0; + if (is_neg) + { + n = - n -1; + } + int signum; + std::vector v; + n.write_bytes_be(signum, v); + + sink_.push_back('\"'); + if (is_neg) + { + sink_.push_back('~'); + ++column_; + } + std::size_t length = encode_base64(v.begin(), v.end(), sink_); + sink_.push_back('\"'); + column_ += (length+2); + break; + } + case bigint_chars_format::base64url: + { + bigint n = bigint::from_string(sv.data(), sv.length()); + bool is_neg = n < 0; + if (is_neg) + { + n = - n -1; + } + int signum; + std::vector v; + n.write_bytes_be(signum, v); + + sink_.push_back('\"'); + if (is_neg) + { + sink_.push_back('~'); + ++column_; + } + std::size_t length = encode_base64url(v.begin(), v.end(), sink_); + sink_.push_back('\"'); + column_ += (length+2); + break; + } + default: + { + sink_.push_back('\"'); + sink_.append(sv.data(),sv.size()); + sink_.push_back('\"'); + column_ += (sv.size() + 2); + break; + } + } + } + + void end_value() + { + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + } + + void indent() + { + indent_amount_ += static_cast(options_.indent_size()); + } + + void unindent() + { + indent_amount_ -= static_cast(options_.indent_size()); + } + + void new_line() + { + sink_.append(options_.new_line_chars().data(),options_.new_line_chars().length()); + for (int i = 0; i < indent_amount_; ++i) + { + sink_.push_back(' '); + } + column_ = indent_amount_; + } + + void new_line(std::size_t len) + { + sink_.append(options_.new_line_chars().data(),options_.new_line_chars().length()); + for (std::size_t i = 0; i < len; ++i) + { + sink_.push_back(' '); + } + column_ = len; + } + + void break_line() + { + stack_.back().new_line_after(true); + new_line(); + } + }; + + template ,typename Allocator=std::allocator> + class basic_compact_json_encoder final : public basic_json_visitor + { + static const std::array& null_constant() + { + static constexpr std::array k{{'n','u','l','l'}}; + return k; + } + static const std::array& true_constant() + { + static constexpr std::array k{{'t','r','u','e'}}; + return k; + } + static const std::array& false_constant() + { + static constexpr std::array k{{'f','a','l','s','e'}}; + return k; + } + public: + using allocator_type = Allocator; + using char_type = CharT; + using typename basic_json_visitor::string_view_type; + using sink_type = Sink; + using string_type = typename basic_json_encode_options::string_type; + + private: + enum class container_type {object, array}; + + class encoding_context + { + container_type type_; + std::size_t count_; + public: + encoding_context(container_type type) noexcept + : type_(type), count_(0) + { + } + + std::size_t count() const + { + return count_; + } + + void increment_count() + { + ++count_; + } + + bool is_array() const + { + return type_ == container_type::array; + } + }; + using encoding_context_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + Sink sink_; + basic_json_encode_options options_; + jsoncons::detail::write_double fp_; + std::vector stack_; + int nesting_depth_; + + // Noncopyable + basic_compact_json_encoder(const basic_compact_json_encoder&) = delete; + basic_compact_json_encoder& operator=(const basic_compact_json_encoder&) = delete; + public: + basic_compact_json_encoder(Sink&& sink, + const Allocator& alloc = Allocator()) + : basic_compact_json_encoder(std::forward(sink), basic_json_encode_options(), alloc) + { + } + + basic_compact_json_encoder(Sink&& sink, + const basic_json_encode_options& options, + const Allocator& alloc = Allocator()) + : sink_(std::forward(sink)), + options_(options), + fp_(options.float_format(), options.precision()), + stack_(alloc), + nesting_depth_(0) + { + } + + basic_compact_json_encoder(basic_compact_json_encoder&&) = default; + basic_compact_json_encoder& operator=(basic_compact_json_encoder&&) = default; + + ~basic_compact_json_encoder() noexcept + { + JSONCONS_TRY + { + sink_.flush(); + } + JSONCONS_CATCH(...) + { + } + } + + void reset() + { + stack_.clear(); + nesting_depth_ = 0; + } + + void reset(Sink&& sink) + { + sink_ = std::move(sink); + reset(); + } + + private: + // Implementing methods + void visit_flush() final + { + sink_.flush(); + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) final + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = json_errc::max_nesting_depth_exceeded; + return false; + } + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + + stack_.emplace_back(container_type::object); + sink_.push_back('{'); + return true; + } + + bool visit_end_object(const ser_context&, std::error_code&) final + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + stack_.pop_back(); + sink_.push_back('}'); + + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) final + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = json_errc::max_nesting_depth_exceeded; + return false; + } + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + stack_.emplace_back(container_type::array); + sink_.push_back('['); + return true; + } + + bool visit_end_array(const ser_context&, std::error_code&) final + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + stack_.pop_back(); + sink_.push_back(']'); + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) final + { + if (!stack_.empty() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + + sink_.push_back('\"'); + jsoncons::detail::escape_string(name.data(), name.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_); + sink_.push_back('\"'); + sink_.push_back(':'); + return true; + } + + bool visit_null(semantic_tag, const ser_context&, std::error_code&) final + { + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + + sink_.append(null_constant().data(), null_constant().size()); + + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + void write_bigint_value(const string_view_type& sv) + { + switch (options_.bigint_format()) + { + case bigint_chars_format::number: + { + sink_.append(sv.data(),sv.size()); + break; + } + case bigint_chars_format::base64: + { + bigint n = bigint::from_string(sv.data(), sv.length()); + bool is_neg = n < 0; + if (is_neg) + { + n = - n -1; + } + int signum; + std::vector v; + n.write_bytes_be(signum, v); + + sink_.push_back('\"'); + if (is_neg) + { + sink_.push_back('~'); + } + encode_base64(v.begin(), v.end(), sink_); + sink_.push_back('\"'); + break; + } + case bigint_chars_format::base64url: + { + bigint n = bigint::from_string(sv.data(), sv.length()); + bool is_neg = n < 0; + if (is_neg) + { + n = - n -1; + } + int signum; + std::vector v; + n.write_bytes_be(signum, v); + + sink_.push_back('\"'); + if (is_neg) + { + sink_.push_back('~'); + } + encode_base64url(v.begin(), v.end(), sink_); + sink_.push_back('\"'); + break; + } + default: + { + sink_.push_back('\"'); + sink_.append(sv.data(),sv.size()); + sink_.push_back('\"'); + break; + } + } + } + + bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code&) final + { + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + + switch (tag) + { + case semantic_tag::bigint: + write_bigint_value(sv); + break; + case semantic_tag::bigdec: + { + // output lossless number + if (options_.bigint_format() == bigint_chars_format::number) + { + write_bigint_value(sv); + break; + } + JSONCONS_FALLTHROUGH; + } + default: + { + sink_.push_back('\"'); + jsoncons::detail::escape_string(sv.data(), sv.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_); + sink_.push_back('\"'); + break; + } + } + + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + bool write_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code&) + { + switch (tag) + { + case semantic_tag::bigint: + write_bigint_value(sv); + break; + case semantic_tag::bigdec: + { + // output lossless number + if (options_.bigint_format() == bigint_chars_format::number) + { + write_bigint_value(sv); + break; + } + JSONCONS_FALLTHROUGH; + } + default: + { + sink_.push_back('\"'); + jsoncons::detail::escape_string(sv.data(), sv.length(),options_.escape_all_non_ascii(),options_.escape_solidus(),sink_); + sink_.push_back('\"'); + break; + } + } + return true; + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context&, + std::error_code&) final + { + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + + byte_string_chars_format encoding_hint; + switch (tag) + { + case semantic_tag::base16: + encoding_hint = byte_string_chars_format::base16; + break; + case semantic_tag::base64: + encoding_hint = byte_string_chars_format::base64; + break; + case semantic_tag::base64url: + encoding_hint = byte_string_chars_format::base64url; + break; + default: + encoding_hint = byte_string_chars_format::none; + break; + } + + byte_string_chars_format format = jsoncons::detail::resolve_byte_string_chars_format(options_.byte_string_format(), + encoding_hint, + byte_string_chars_format::base64url); + switch (format) + { + case byte_string_chars_format::base16: + { + sink_.push_back('\"'); + encode_base16(b.begin(),b.end(),sink_); + sink_.push_back('\"'); + break; + } + case byte_string_chars_format::base64: + { + sink_.push_back('\"'); + encode_base64(b.begin(), b.end(), sink_); + sink_.push_back('\"'); + break; + } + case byte_string_chars_format::base64url: + { + sink_.push_back('\"'); + encode_base64url(b.begin(),b.end(),sink_); + sink_.push_back('\"'); + break; + } + default: + { + JSONCONS_UNREACHABLE(); + } + } + + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + bool visit_double(double value, + semantic_tag, + const ser_context& context, + std::error_code& ec) final + { + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + + if (JSONCONS_UNLIKELY(!std::isfinite(value))) + { + if ((std::isnan)(value)) + { + if (options_.enable_nan_to_num()) + { + sink_.append(options_.nan_to_num().data(), options_.nan_to_num().length()); + } + else if (options_.enable_nan_to_str()) + { + write_string(options_.nan_to_str(), semantic_tag::none, context, ec); + } + else + { + sink_.append(null_constant().data(), null_constant().size()); + } + } + else if (value == std::numeric_limits::infinity()) + { + if (options_.enable_inf_to_num()) + { + sink_.append(options_.inf_to_num().data(), options_.inf_to_num().length()); + } + else if (options_.enable_inf_to_str()) + { + write_string(options_.inf_to_str(), semantic_tag::none, context, ec); + } + else + { + sink_.append(null_constant().data(), null_constant().size()); + } + } + else + { + if (options_.enable_neginf_to_num()) + { + sink_.append(options_.neginf_to_num().data(), options_.neginf_to_num().length()); + } + else if (options_.enable_neginf_to_str()) + { + write_string(options_.neginf_to_str(), semantic_tag::none, context, ec); + } + else + { + sink_.append(null_constant().data(), null_constant().size()); + } + } + } + else + { + fp_(value, sink_); + } + + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + bool visit_int64(int64_t value, + semantic_tag, + const ser_context&, + std::error_code&) final + { + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + jsoncons::detail::from_integer(value, sink_); + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + bool visit_uint64(uint64_t value, + semantic_tag, + const ser_context&, + std::error_code&) final + { + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + jsoncons::detail::from_integer(value, sink_); + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + + bool visit_bool(bool value, semantic_tag, const ser_context&, std::error_code&) final + { + if (!stack_.empty() && stack_.back().is_array() && stack_.back().count() > 0) + { + sink_.push_back(','); + } + + if (value) + { + sink_.append(true_constant().data(), true_constant().size()); + } + else + { + sink_.append(false_constant().data(), false_constant().size()); + } + + if (!stack_.empty()) + { + stack_.back().increment_count(); + } + return true; + } + }; + + using json_stream_encoder = basic_json_encoder>; + using wjson_stream_encoder = basic_json_encoder>; + using compact_json_stream_encoder = basic_compact_json_encoder>; + using compact_wjson_stream_encoder = basic_compact_json_encoder>; + + using json_string_encoder = basic_json_encoder>; + using wjson_string_encoder = basic_json_encoder>; + using compact_json_string_encoder = basic_compact_json_encoder>; + using compact_wjson_string_encoder = basic_compact_json_encoder>; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/json_error.hpp b/third_party/jsoncons/json_error.hpp new file mode 100644 index 0000000000..e3eb18ad4d --- /dev/null +++ b/third_party/jsoncons/json_error.hpp @@ -0,0 +1,150 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_ERROR_HPP +#define JSONCONS_JSON_ERROR_HPP + +#include +#include + +namespace jsoncons { + + enum class json_errc + { + success = 0, + unexpected_eof = 1, + source_error, + syntax_error, + extra_character, + max_nesting_depth_exceeded, + single_quote, + illegal_character_in_string, + extra_comma, + expected_key, + expected_value, + invalid_value, + expected_colon, + illegal_control_character, + illegal_escaped_character, + expected_codepoint_surrogate_pair, + invalid_hex_escape_sequence, + invalid_unicode_escape_sequence, + leading_zero, + invalid_number, + expected_comma_or_rbrace, + expected_comma_or_rbracket, + unexpected_rbracket, + unexpected_rbrace, + illegal_comment, + expected_continuation_byte, + over_long_utf8_sequence, + illegal_codepoint, + illegal_surrogate_value, + unpaired_high_surrogate, + illegal_unicode_character + }; + + class json_error_category_impl + : public std::error_category + { + public: + const char* name() const noexcept final + { + return "jsoncons/json"; + } + std::string message(int ev) const final + { + switch (static_cast(ev)) + { + case json_errc::unexpected_eof: + return "Unexpected end of file"; + case json_errc::source_error: + return "Source error"; + case json_errc::syntax_error: + return "JSON syntax_error"; + case json_errc::extra_character: + return "Unexpected non-whitespace character after JSON text"; + case json_errc::max_nesting_depth_exceeded: + return "Data item nesting exceeds limit in options"; + case json_errc::single_quote: + return "JSON strings cannot be quoted with single quotes"; + case json_errc::illegal_character_in_string: + return "Illegal character in string"; + case json_errc::extra_comma: + return "Extra comma"; + case json_errc::expected_key: + return "Expected object member key"; + case json_errc::expected_value: + return "Expected value"; + case json_errc::invalid_value: + return "Invalid value"; + case json_errc::expected_colon: + return "Expected name separator ':'"; + case json_errc::illegal_control_character: + return "Illegal control character in string"; + case json_errc::illegal_escaped_character: + return "Illegal escaped character in string"; + case json_errc::expected_codepoint_surrogate_pair: + return "Invalid codepoint, expected another \\u token to begin the second half of a codepoint surrogate pair."; + case json_errc::invalid_hex_escape_sequence: + return "Invalid codepoint, expected hexadecimal digit."; + case json_errc::invalid_unicode_escape_sequence: + return "Invalid codepoint, expected four hexadecimal digits."; + case json_errc::leading_zero: + return "A number cannot have a leading zero"; + case json_errc::invalid_number: + return "Invalid number"; + case json_errc::expected_comma_or_rbrace: + return "Expected comma or right brace '}'"; + case json_errc::expected_comma_or_rbracket: + return "Expected comma or right bracket ']'"; + case json_errc::unexpected_rbrace: + return "Unexpected right brace '}'"; + case json_errc::unexpected_rbracket: + return "Unexpected right bracket ']'"; + case json_errc::illegal_comment: + return "Illegal comment"; + case json_errc::expected_continuation_byte: + return "Expected continuation byte"; + case json_errc::over_long_utf8_sequence: + return "Over long UTF-8 sequence"; + case json_errc::illegal_codepoint: + return "Illegal codepoint (>= 0xd800 && <= 0xdfff)"; + case json_errc::illegal_surrogate_value: + return "UTF-16 surrogate values are illegal in UTF-32"; + case json_errc::unpaired_high_surrogate: + return "Expected low surrogate following the high surrogate"; + case json_errc::illegal_unicode_character: + return "Illegal unicode character"; + default: + return "Unknown JSON parser error"; + } + } + }; + + inline + const std::error_category& json_error_category() + { + static json_error_category_impl instance; + return instance; + } + + inline + std::error_code make_error_code(json_errc result) + { + return std::error_code(static_cast(result),json_error_category()); + } + +} // jsoncons + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons/json_exception.hpp b/third_party/jsoncons/json_exception.hpp new file mode 100644 index 0000000000..430c77b082 --- /dev/null +++ b/third_party/jsoncons/json_exception.hpp @@ -0,0 +1,220 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSON_EXCEPTION_HPP +#define JSON_EXCEPTION_HPP + +#include // std::string +#include // std::ostringstream +#include // std::error_code +#include // unicode_traits::convert +#include +#include + +namespace jsoncons { + + // json_exception + + class json_exception + { + public: + virtual ~json_exception() noexcept = default; + virtual const char* what() const noexcept = 0; + }; + + // json_runtime_error + + template + class json_runtime_error + { + }; + + template + class json_runtime_error::value && + extension_traits::is_constructible_from_string::value>::type> + : public Base, public virtual json_exception + { + public: + json_runtime_error(const std::string& s) noexcept + : Base(s) + { + } + ~json_runtime_error() noexcept + { + } + const char* what() const noexcept override + { + return Base::what(); + } + }; + + class key_not_found : public std::out_of_range, public virtual json_exception + { + std::string name_; + mutable std::string what_; + public: + template + explicit key_not_found(const CharT* key, std::size_t length) noexcept + : std::out_of_range("Key not found") + { + JSONCONS_TRY + { + unicode_traits::convert(key, length, name_, + unicode_traits::conv_flags::strict); + } + JSONCONS_CATCH(...) + { + } + } + + virtual ~key_not_found() noexcept + { + } + + const char* what() const noexcept override + { + if (what_.empty()) + { + JSONCONS_TRY + { + what_.append(std::out_of_range::what()); + what_.append(": '"); + what_.append(name_); + what_.append("'"); + return what_.c_str(); + } + JSONCONS_CATCH(...) + { + return std::out_of_range::what(); + } + } + else + { + return what_.c_str(); + } + } + }; + + class not_an_object : public std::runtime_error, public virtual json_exception + { + std::string name_; + mutable std::string what_; + public: + template + explicit not_an_object(const CharT* key, std::size_t length) noexcept + : std::runtime_error("Attempting to access a member of a value that is not an object") + { + JSONCONS_TRY + { + unicode_traits::convert(key, length, name_, + unicode_traits::conv_flags::strict); + } + JSONCONS_CATCH(...) + { + } + } + + virtual ~not_an_object() noexcept + { + } + const char* what() const noexcept override + { + if (what_.empty()) + { + JSONCONS_TRY + { + what_.append(std::runtime_error::what()); + what_.append(": '"); + what_.append(name_); + what_.append("'"); + return what_.c_str(); + } + JSONCONS_CATCH(...) + { + return std::runtime_error::what(); + } + } + else + { + return what_.c_str(); + } + } + }; + + class ser_error : public std::system_error, public virtual json_exception + { + std::size_t line_number_; + std::size_t column_number_; + mutable std::string what_; + public: + ser_error(std::error_code ec) + : std::system_error(ec), line_number_(0), column_number_(0) + { + } + ser_error(std::error_code ec, const std::string& what_arg) + : std::system_error(ec, what_arg), line_number_(0), column_number_(0) + { + } + ser_error(std::error_code ec, std::size_t position) + : std::system_error(ec), line_number_(0), column_number_(position) + { + } + ser_error(std::error_code ec, std::size_t line, std::size_t column) + : std::system_error(ec), line_number_(line), column_number_(column) + { + } + ser_error(const ser_error& other) = default; + + ser_error(ser_error&& other) = default; + + const char* what() const noexcept override + { + if (what_.empty()) + { + JSONCONS_TRY + { + what_.append(std::system_error::what()); + if (line_number_ != 0 && column_number_ != 0) + { + what_.append(" at line "); + what_.append(std::to_string(line_number_)); + what_.append(" and column "); + what_.append(std::to_string(column_number_)); + } + else if (column_number_ != 0) + { + what_.append(" at position "); + what_.append(std::to_string(column_number_)); + } + return what_.c_str(); + } + JSONCONS_CATCH(...) + { + return std::system_error::what(); + } + } + else + { + return what_.c_str(); + } + } + + std::size_t line() const noexcept + { + return line_number_; + } + + std::size_t column() const noexcept + { + return column_number_; + } + + }; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/json_filter.hpp b/third_party/jsoncons/json_filter.hpp new file mode 100644 index 0000000000..fa672eabdb --- /dev/null +++ b/third_party/jsoncons/json_filter.hpp @@ -0,0 +1,609 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_FILTER_HPP +#define JSONCONS_JSON_FILTER_HPP + +#include + +#include + +namespace jsoncons { + +template +class basic_json_filter : public basic_json_visitor +{ +public: + using typename basic_json_visitor::char_type; + using typename basic_json_visitor::string_view_type; +private: + basic_json_visitor* destination_; + +public: + basic_json_filter(basic_json_visitor& visitor) + : destination_(std::addressof(visitor)) + { + } + + basic_json_visitor& destination() + { + return *destination_; + } + +private: + void visit_flush() override + { + destination_->flush(); + } + + bool visit_begin_object(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->begin_object(tag, context, ec); + } + + bool visit_begin_object(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->begin_object(length, tag, context, ec); + } + + bool visit_end_object(const ser_context& context, std::error_code& ec) override + { + return destination_->end_object(context, ec); + } + + bool visit_begin_array(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->begin_array(tag, context, ec); + } + + bool visit_begin_array(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->begin_array(length, tag, context, ec); + } + + bool visit_end_array(const ser_context& context, std::error_code& ec) override + { + return destination_->end_array(context, ec); + } + + bool visit_key(const string_view_type& name, + const ser_context& context, + std::error_code& ec) override + { + return destination_->key(name, context, ec); + } + + bool visit_string(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->string_value(value, tag, context, ec); + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->byte_string_value(b, tag, context, ec); + } + + bool visit_byte_string(const byte_string_view& b, + uint64_t ext_tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->byte_string_value(b, ext_tag, context, ec); + } + + bool visit_uint64(uint64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->uint64_value(value, tag, context, ec); + } + + bool visit_int64(int64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->int64_value(value, tag, context, ec); + } + + bool visit_half(uint16_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->half_value(value, tag, context, ec); + } + + bool visit_double(double value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->double_value(value, tag, context, ec); + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->bool_value(value, tag, context, ec); + } + + bool visit_null(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->null_value(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(half_arg_t, + const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(half_arg, s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->begin_multi_dim(shape, tag, context, ec); + } + + bool visit_end_multi_dim(const ser_context& context, + std::error_code& ec) override + { + return destination_->end_multi_dim(context, ec); + } +}; + +template +class basic_rename_object_key_filter : public basic_json_filter +{ +public: + using typename basic_json_filter::string_view_type; + +private: + std::basic_string name_; + std::basic_string new_name_; +public: + basic_rename_object_key_filter(const std::basic_string& name, + const std::basic_string& new_name, + basic_json_visitor& visitor) + : basic_json_filter(visitor), + name_(name), new_name_(new_name) + { + } + +private: + bool visit_key(const string_view_type& name, + const ser_context& context, + std::error_code& ec) override + { + if (name == name_) + { + return this->destination().key(new_name_,context, ec); + } + else + { + return this->destination().key(name,context,ec); + } + } +}; + +template +class json_visitor_adaptor_base : public From +{ +public: + using typename From::string_view_type; +private: + To* destination_; + + // noncopyable + json_visitor_adaptor_base(const json_visitor_adaptor_base&) = delete; + json_visitor_adaptor_base& operator=(const json_visitor_adaptor_base&) = delete; +public: + json_visitor_adaptor_base(To& visitor) + : destination_(std::addressof(visitor)) + { + } + + // moveable + json_visitor_adaptor_base(json_visitor_adaptor_base&&) = default; + json_visitor_adaptor_base& operator=(json_visitor_adaptor_base&&) = default; + + To& destination() + { + return *destination_; + } + +private: + void visit_flush() override + { + destination_->flush(); + } + + bool visit_begin_object(semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->begin_object(tag, context, ec); + } + + bool visit_begin_object(std::size_t length, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->begin_object(length, tag, context, ec); + } + + bool visit_end_object(const ser_context& context, std::error_code& ec) override + { + return destination_->end_object(context, ec); + } + + bool visit_begin_array(semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->begin_array(tag, context, ec); + } + + bool visit_begin_array(std::size_t length, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->begin_array(length, tag, context, ec); + } + + bool visit_end_array(const ser_context& context, std::error_code& ec) override + { + return destination_->end_array(context, ec); + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->byte_string_value(b, tag, context, ec); + } + + bool visit_byte_string(const byte_string_view& b, + uint64_t ext_tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->byte_string_value(b, ext_tag, context, ec); + } + + bool visit_half(uint16_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->half_value(value, tag, context, ec); + } + + bool visit_double(double value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->double_value(value, tag, context, ec); + } + + bool visit_int64(int64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->int64_value(value, tag, context, ec); + } + + bool visit_uint64(uint64_t value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->uint64_value(value, tag, context, ec); + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->bool_value(value, tag, context, ec); + } + + bool visit_null(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return destination_->null_value(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(half_arg_t, + const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(half_arg, s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->typed_array(s, tag, context, ec); + } + + bool visit_begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination_->begin_multi_dim(shape, tag, context, ec); + } + + bool visit_end_multi_dim(const ser_context& context, + std::error_code& ec) override + { + return destination_->end_multi_dim(context, ec); + } + +}; + +template +class json_visitor_adaptor +{ +}; + +template +class json_visitor_adaptor::value && + extension_traits::is_narrow_character::value>::type> : public json_visitor_adaptor_base +{ + using supertype = json_visitor_adaptor_base; + using to_char_type = typename To::char_type; + using from_char_type = typename From::char_type; +public: + using typename From::string_view_type; + using supertype::destination; +private: + + // noncopyable + json_visitor_adaptor(const json_visitor_adaptor&) = delete; + json_visitor_adaptor& operator=(const json_visitor_adaptor&) = delete; +public: + json_visitor_adaptor(To& visitor) + : supertype(visitor) + { + } + + // moveable + json_visitor_adaptor(json_visitor_adaptor&&) = default; + json_visitor_adaptor& operator=(json_visitor_adaptor&&) = default; + +private: + + bool visit_key(const string_view_type& key, + const ser_context& context, + std::error_code& ec) override + { + return destination().key(string_view_type(reinterpret_cast(key.data()),key.size()), context, ec); + } + + bool visit_string(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return destination().string_value(string_view_type(reinterpret_cast(value.data()),value.size()), tag, context, ec); + } +}; + +template +class json_visitor_adaptor::value && + extension_traits::is_narrow_character::value)>::type> : public json_visitor_adaptor_base +{ + using supertype = json_visitor_adaptor_base; +public: + using typename From::string_view_type; + using supertype::destination; +private: + + // noncopyable + json_visitor_adaptor(const json_visitor_adaptor&) = delete; + json_visitor_adaptor& operator=(const json_visitor_adaptor&) = delete; +public: + json_visitor_adaptor(To& visitor) + : supertype(visitor) + { + } + + // moveable + json_visitor_adaptor(json_visitor_adaptor&&) = default; + json_visitor_adaptor& operator=(json_visitor_adaptor&&) = default; + +private: + + bool visit_key(const string_view_type& name, + const ser_context& context, + std::error_code& ec) override + { + std::basic_string target; + auto result = unicode_traits::convert(name.data(), name.size(), target, unicode_traits::conv_flags::strict); + if (result.ec != unicode_traits::conv_errc()) + { + ec = result.ec; + } + return destination().key(target, context, ec); + } + + bool visit_string(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + std::basic_string target; + auto result = unicode_traits::convert(value.data(), value.size(), + target,unicode_traits::conv_flags::strict); + if (result.ec != unicode_traits::conv_errc()) + { + JSONCONS_THROW(ser_error(result.ec)); + } + return destination().string_value(target, tag, context, ec); + } +}; + +template +json_visitor_adaptor make_json_visitor_adaptor(To& to) +{ + return json_visitor_adaptor(to); +} + +using json_filter = basic_json_filter; +using wjson_filter = basic_json_filter; +using rename_object_key_filter = basic_rename_object_key_filter; +using wrename_object_key_filter = basic_rename_object_key_filter; + +} + +#endif diff --git a/third_party/jsoncons/json_fwd.hpp b/third_party/jsoncons/json_fwd.hpp new file mode 100644 index 0000000000..f23eb1a5ea --- /dev/null +++ b/third_party/jsoncons/json_fwd.hpp @@ -0,0 +1,23 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_FWD_HPP +#define JSONCONS_JSON_FWD_HPP + +#include // std::allocator + +namespace jsoncons { + +struct sorted_policy; + +template > +class basic_json; + +} + +#endif diff --git a/third_party/jsoncons/json_object.hpp b/third_party/jsoncons/json_object.hpp new file mode 100644 index 0000000000..cc4c57c387 --- /dev/null +++ b/third_party/jsoncons/json_object.hpp @@ -0,0 +1,1720 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_OBJECT_HPP +#define JSONCONS_JSON_OBJECT_HPP + +#include +#include +#include +#include +#include +#include // std::sort, std::stable_sort, std::lower_bound, std::unique +#include +#include +#include // std::iterator_traits +#include // std::allocator +#include // std::move +#include // assert +#include +#include // std::enable_if +#include +#include +#include + +namespace jsoncons { + + template + struct index_key_value + { + using key_type = typename Json::key_type; + using allocator_type = typename Json::allocator_type; + + key_type name; + int64_t index; + Json value; + + template + index_key_value(key_type&& Name, int64_t Index, Args&& ... args) + : name(std::move(Name)), index(Index), value(std::forward(args)...) + { + } + + index_key_value() = default; + index_key_value(const index_key_value&) = default; + index_key_value(index_key_value&&) = default; + index_key_value(const index_key_value& other, const allocator_type& alloc) + : name(other.name, alloc), index(0), value(other.value, alloc) + { + } + index_key_value(index_key_value&& other, const allocator_type& alloc) + : name(std::move(other.name), alloc), index(0), value(std::move(other.value), alloc) + { + + } + index_key_value& operator=(const index_key_value&) = default; + index_key_value& operator=(index_key_value&&) = default; + }; + + struct sorted_unique_range_tag + { + explicit sorted_unique_range_tag() = default; + }; + + // key_value + + template + class key_value + { + public: + using key_type = KeyT; + using value_type = ValueT; + using string_view_type = typename value_type::string_view_type; + using allocator_type = typename ValueT::allocator_type; + private: + + key_type key_; + value_type value_; + public: + + key_value() noexcept + { + } + + template + key_value(key_type&& name, Args&& ... args) noexcept + : key_(std::move(name)), value_(std::forward(args)...) + { + } + key_value(const key_value& member) + : key_(member.key_), value_(member.value_) + { + } + + key_value(const key_value& member, const allocator_type& alloc) + : key_(member.key_, alloc), value_(member.value_, alloc) + { + } + + key_value(key_value&& member) noexcept + : key_(std::move(member.key_)), value_(std::move(member.value_)) + { + } + + key_value(key_value&& member, const allocator_type& alloc) noexcept + : key_(std::move(member.key_), alloc), value_(std::move(member.value_), alloc) + { + } + + const key_type& key() const + { + return key_; + } + + value_type& value() + { + return value_; + } + + const value_type& value() const + { + return value_; + } + + template + void value(T&& newValue) + { + value_ = std::forward(newValue); + } + + void swap(key_value& member) noexcept + { + key_.swap(member.key_); + value_.swap(member.value_); + } + + key_value& operator=(const key_value& member) + { + if (this != & member) + { + key_ = member.key_; + value_ = member.value_; + } + return *this; + } + + key_value& operator=(key_value&& member) noexcept + { + if (this != &member) + { + key_.swap(member.key_); + value_.swap(member.value_); + } + return *this; + } + + void shrink_to_fit() + { + key_.shrink_to_fit(); + value_.shrink_to_fit(); + } + + friend bool operator==(const key_value& lhs, const key_value& rhs) noexcept + { + return lhs.key_ == rhs.key_ && lhs.value_ == rhs.value_; + } + + friend bool operator!=(const key_value& lhs, const key_value& rhs) noexcept + { + return !(lhs == rhs); + } + + friend bool operator<(const key_value& lhs, const key_value& rhs) noexcept + { + if (lhs.key_ < rhs.key_) + { + return true; + } + if (lhs.key_ == rhs.key_ && lhs.value_ < rhs.value_) + { + return true; + } + return false; + } + + friend bool operator<=(const key_value& lhs, const key_value& rhs) noexcept + { + return !(rhs < lhs); + } + + friend bool operator>(const key_value& lhs, const key_value& rhs) noexcept + { + return !(lhs <= rhs); + } + + friend bool operator>=(const key_value& lhs, const key_value& rhs) noexcept + { + return !(lhs < rhs); + } + + friend void swap(key_value& a, key_value& b) noexcept( + noexcept(std::declval().swap(std::declval()))) + { + a.swap(b); + } + }; + + // Structured Bindings Support + // See https://blog.tartanllama.xyz/structured-bindings/ + template::type = 0> + auto get(const key_value& i) -> decltype(i.key()) + { + return i.key(); + } + // Structured Bindings Support + // See https://blog.tartanllama.xyz/structured-bindings/ + template::type = 0> + auto get(const key_value& i) -> decltype(i.value()) + { + return i.value(); + } + +} // jsoncons + +namespace std +{ +#if defined(__clang__) + // Fix: https://github.com/nlohmann/json/issues/1401 + #pragma clang diagnostic push + #pragma clang diagnostic ignored "-Wmismatched-tags" +#endif + + template + struct tuple_size> + : public std::integral_constant {}; + + template struct tuple_element<0, jsoncons::key_value> { using type = Key; }; + template struct tuple_element<1, jsoncons::key_value> { using type = Value; }; + +#if defined(__clang__) + #pragma clang diagnostic pop +#endif + +} // namespace std + +namespace jsoncons { + + template + struct get_key_value + { + using key_value_type = key_value; + + template + key_value_type operator()(const std::pair& p) // Remove + { + return key_value_type(p.first,p.second); + } + template + key_value_type operator()(std::pair&& p) + { + return key_value_type(std::forward(p.first),std::forward(p.second)); + } + template + const key_value_type& operator()(const key_value& p) + { + return p; + } + template + key_value_type operator()(key_value&& p) + { + return std::move(p); + } + }; + + struct sort_key_order + { + explicit sort_key_order() = default; + }; + + struct preserve_key_order + { + explicit preserve_key_order() = default; + }; + + + // Sort keys + template class SequenceContainer = std::vector> + class sorted_json_object : public allocator_holder + { + public: + using allocator_type = typename Json::allocator_type; + using key_type = KeyT; + using key_value_type = key_value; + using char_type = typename Json::char_type; + using string_view_type = typename Json::string_view_type; + private: + struct Comp + { + bool operator() (const key_value_type& kv, string_view_type k) const { return kv.key() < k; } + bool operator() (string_view_type k, const key_value_type& kv) const { return k < kv.key(); } + }; + + using key_value_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using key_value_container_type = SequenceContainer; + + key_value_container_type members_; + public: + using iterator = typename key_value_container_type::iterator; + using const_iterator = typename key_value_container_type::const_iterator; + + using allocator_holder::get_allocator; + + sorted_json_object() + { + } + + explicit sorted_json_object(const allocator_type& alloc) + : allocator_holder(alloc), + members_(key_value_allocator_type(alloc)) + { + } + + sorted_json_object(const sorted_json_object& other) + : allocator_holder(other.get_allocator()), + members_(other.members_) + { + } + + sorted_json_object(const sorted_json_object& other, const allocator_type& alloc) + : allocator_holder(alloc), + members_(other.members_,key_value_allocator_type(alloc)) + { + } + + sorted_json_object(sorted_json_object&& other) noexcept + : allocator_holder(other.get_allocator()), + members_(std::move(other.members_)) + { + } + + sorted_json_object(sorted_json_object&& other,const allocator_type& alloc) + : allocator_holder(alloc), members_(std::move(other.members_),key_value_allocator_type(alloc)) + { + } + + sorted_json_object& operator=(const sorted_json_object& other) + { + allocator_holder::operator=(other.get_allocator()); + members_ = other.members_; + return *this; + } + + sorted_json_object& operator=(sorted_json_object&& other) + { + other.swap(*this); + return *this; + } + + template + sorted_json_object(InputIt first, InputIt last) + { + std::size_t count = std::distance(first,last); + members_.reserve(count); + for (auto s = first; s != last; ++s) + { + members_.emplace_back(key_type(s->first,get_allocator()), s->second); + } + std::stable_sort(members_.begin(),members_.end(), + [](const key_value_type& a, const key_value_type& b) -> bool {return a.key().compare(b.key()) < 0;}); + auto it = std::unique(members_.begin(), members_.end(), + [](const key_value_type& a, const key_value_type& b) -> bool { return !(a.key().compare(b.key()));}); + members_.erase(it, members_.end()); + } + + template + sorted_json_object(InputIt first, InputIt last, + const allocator_type& alloc) + : allocator_holder(alloc), + members_(key_value_allocator_type(alloc)) + { + std::size_t count = std::distance(first,last); + members_.reserve(count); + for (auto s = first; s != last; ++s) + { + members_.emplace_back(key_type(s->first.c_str(), s->first.size(), get_allocator()), s->second); + } + std::stable_sort(members_.begin(), members_.end(), + [](const key_value_type& a, const key_value_type& b) -> bool {return a.key().compare(b.key()) < 0;}); + auto it = std::unique(members_.begin(), members_.end(), + [](const key_value_type& a, const key_value_type& b) -> bool { return !(a.key().compare(b.key()));}); + members_.erase(it, members_.end()); + } + + sorted_json_object(const std::initializer_list,Json>>& init, + const allocator_type& alloc = allocator_type()) + : allocator_holder(alloc), + members_(key_value_allocator_type(alloc)) + { + members_.reserve(init.size()); + for (auto& item : init) + { + insert_or_assign(item.first, item.second); + } + } + + ~sorted_json_object() noexcept + { + flatten_and_destroy(); + } + + bool empty() const + { + return members_.empty(); + } + + void swap(sorted_json_object& other) noexcept + { + members_.swap(other.members_); + } + + iterator begin() + { + return members_.begin(); + } + + iterator end() + { + return members_.end(); + } + + const_iterator begin() const + { + return members_.begin(); + } + + const_iterator end() const + { + return members_.end(); + } + + std::size_t size() const {return members_.size();} + + std::size_t capacity() const {return members_.capacity();} + + void clear() {members_.clear();} + + void shrink_to_fit() + { + for (std::size_t i = 0; i < members_.size(); ++i) + { + members_[i].shrink_to_fit(); + } + members_.shrink_to_fit(); + } + + void reserve(std::size_t n) {members_.reserve(n);} + + Json& at(std::size_t i) + { + if (i >= members_.size()) + { + JSONCONS_THROW(json_runtime_error("Invalid array subscript")); + } + return members_[i].value(); + } + + const Json& at(std::size_t i) const + { + if (i >= members_.size()) + { + JSONCONS_THROW(json_runtime_error("Invalid array subscript")); + } + return members_[i].value(); + } + + iterator find(const string_view_type& name) noexcept + { + auto p = std::equal_range(members_.begin(),members_.end(), name, + Comp()); + return p.first == p.second ? members_.end() : p.first; + } + + const_iterator find(const string_view_type& name) const noexcept + { + auto p = std::equal_range(members_.begin(),members_.end(), name, + Comp()); + return p.first == p.second ? members_.end() : p.first; + } + + iterator erase(const_iterator pos) + { + return members_.erase(pos); + } + + iterator erase(const_iterator first, const_iterator last) + { + return members_.erase(first,last); + } + + void erase(const string_view_type& name) + { + auto it = find(name); + if (it != members_.end()) + { + members_.erase(it); + } + } + + static bool compare(const index_key_value& item1, const index_key_value& item2) + { + int comp = item1.name.compare(item2.name); + if (comp < 0) return true; + if (comp == 0) return item1.index < item2.index; + + return false; + } + + void uninitialized_init(index_key_value* items, std::size_t count) + { + auto first = items; + if (count > 0) + { + members_.reserve(count); + + auto last = first + count; + + std::sort(first, last, compare); + members_.emplace_back(key_type(first->name.c_str(), first->name.size(), get_allocator()), std::move(first->value)); + auto prev_it = first; + for (auto it = first+1; it != last; ++it) + { + if (it->name != prev_it->name) + { + members_.emplace_back(key_type(it->name.c_str(), it->name.size(), get_allocator()), std::move(it->value)); + } + ++prev_it; + } + } + } + + template + void insert(InputIt first, InputIt last) + { + for (auto s = first; s != last; ++s) + { + members_.emplace_back(key_type(s->first.c_str(), s->first.size(), get_allocator()), s->second); + } + std::stable_sort(members_.begin(),members_.end(), + [](const key_value_type& a, const key_value_type& b) -> bool {return a.key().compare(b.key()) < 0;}); + auto it = std::unique(members_.begin(), members_.end(), + [](const key_value_type& a, const key_value_type& b) -> bool { return !(a.key().compare(b.key()));}); + members_.erase(it, members_.end()); + } + + template + void insert(sorted_unique_range_tag, InputIt first, InputIt last) + { + if (first != last) + { + auto it = find(convert(*first).key()); + if (it != members_.end()) + { + for (auto s = first; s != last; ++s) + { + it = members_.emplace(it, key_type(s->first, get_allocator()), s->second); + } + } + else + { + for (auto s = first; s != last; ++s) + { + members_.emplace_back(convert(*s)); + } + } + } + } + + // insert_or_assign + + template + typename std::enable_if::value,std::pair>::type + insert_or_assign(const string_view_type& name, T&& value) + { + bool inserted; + auto it = std::lower_bound(members_.begin(),members_.end(), name, Comp()); + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end()), std::forward(value)); + inserted = true; + it = members_.begin() + members_.size() - 1; + } + else if (it->key() == name) + { + it->value(Json(std::forward(value))); + inserted = false; // assigned + } + else + { + it = members_.emplace(it, key_type(name.begin(),name.end()), std::forward(value)); + inserted = true; + } + return std::make_pair(it,inserted); + } + + template + typename std::enable_if::value,std::pair>::type + insert_or_assign(const string_view_type& name, T&& value) + { + bool inserted; + auto it = std::lower_bound(members_.begin(),members_.end(), name, + Comp()); + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end(), get_allocator()), std::forward(value)); + inserted = true; + it = members_.begin() + members_.size() - 1; + } + else if (it->key() == name) + { + it->value(Json(std::forward(value), get_allocator())); + inserted = false; // assigned + } + else + { + it = members_.emplace(it, key_type(name.begin(),name.end(), get_allocator()), + std::forward(value)); + inserted = true; + } + return std::make_pair(it,inserted); + } + + // try_emplace + + template + typename std::enable_if::value,std::pair>::type + try_emplace(const string_view_type& name, Args&&... args) + { + bool inserted; + auto it = std::lower_bound(members_.begin(),members_.end(), name, + Comp()); + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end()), std::forward(args)...); + it = members_.begin() + members_.size() - 1; + inserted = true; + } + else if (it->key() == name) + { + inserted = false; + } + else + { + it = members_.emplace(it, key_type(name.begin(),name.end()), + std::forward(args)...); + inserted = true; + } + return std::make_pair(it,inserted); + } + + template + typename std::enable_if::value,std::pair>::type + try_emplace(const string_view_type& name, Args&&... args) + { + bool inserted; + auto it = std::lower_bound(members_.begin(),members_.end(), name, + Comp()); + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end(), get_allocator()), std::forward(args)...); + it = members_.begin() + members_.size() - 1; + inserted = true; + } + else if (it->key() == name) + { + inserted = false; + } + else + { + it = members_.emplace(it, + key_type(name.begin(),name.end(), get_allocator()), + std::forward(args)...); + inserted = true; + } + return std::make_pair(it,inserted); + } + + template + typename std::enable_if::value,iterator>::type + try_emplace(iterator hint, const string_view_type& name, Args&&... args) + { + iterator it = hint; + + if (hint != members_.end() && hint->key() <= name) + { + it = std::lower_bound(hint,members_.end(), name, + Comp()); + } + else + { + it = std::lower_bound(members_.begin(),members_.end(), name, + Comp()); + } + + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end()), + std::forward(args)...); + it = members_.begin() + (members_.size() - 1); + } + else if (it->key() == name) + { + } + else + { + it = members_.emplace(it, + key_type(name.begin(),name.end()), + std::forward(args)...); + } + + return it; + } + + template + typename std::enable_if::value,iterator>::type + try_emplace(iterator hint, const string_view_type& name, Args&&... args) + { + iterator it = hint; + if (hint != members_.end() && hint->key() <= name) + { + it = std::lower_bound(hint,members_.end(), name, + Comp()); + } + else + { + it = std::lower_bound(members_.begin(),members_.end(), name, + Comp()); + } + + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end(), get_allocator()), + std::forward(args)...); + it = members_.begin() + (members_.size() - 1); + } + else if (it->key() == name) + { + } + else + { + it = members_.emplace(it, + key_type(name.begin(),name.end(), get_allocator()), + std::forward(args)...); + } + return it; + } + + // insert_or_assign + + template + typename std::enable_if::value,iterator>::type + insert_or_assign(iterator hint, const string_view_type& name, T&& value) + { + iterator it; + if (hint != members_.end() && hint->key() <= name) + { + it = std::lower_bound(hint,members_.end(), name, + [](const key_value_type& a, const string_view_type& k) -> bool {return string_view_type(a.key()).compare(k) < 0;}); + } + else + { + it = std::lower_bound(members_.begin(),members_.end(), name, + [](const key_value_type& a, const string_view_type& k) -> bool {return string_view_type(a.key()).compare(k) < 0;}); + } + + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end()), std::forward(value)); + it = members_.begin() + (members_.size() - 1); + } + else if (it->key() == name) + { + it->value(Json(std::forward(value))); + } + else + { + it = members_.emplace(it, key_type(name.begin(),name.end()), std::forward(value)); + } + return it; + } + + template + typename std::enable_if::value,iterator>::type + insert_or_assign(iterator hint, const string_view_type& name, T&& value) + { + iterator it; + if (hint != members_.end() && hint->key() <= name) + { + it = std::lower_bound(hint,members_.end(), name, + Comp()); + } + else + { + it = std::lower_bound(members_.begin(),members_.end(), name, + Comp()); + } + + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end(), get_allocator()), std::forward(value)); + it = members_.begin() + (members_.size() - 1); + } + else if (it->key() == name) + { + it->value(Json(std::forward(value),get_allocator())); + } + else + { + it = members_.emplace(it, key_type(name.begin(),name.end(), get_allocator()), + std::forward(value)); + } + return it; + } + + // merge + + void merge(const sorted_json_object& source) + { + for (auto it = source.begin(); it != source.end(); ++it) + { + try_emplace(it->key(),it->value()); + } + } + + void merge(sorted_json_object&& source) + { + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + auto pos = std::lower_bound(members_.begin(),members_.end(), (*it).key(), + Comp()); + if (pos == members_.end() ) + { + members_.emplace_back(*it); + } + else if ((*it).key() != pos->key()) + { + members_.emplace(pos,*it); + } + } + } + + void merge(iterator hint, const sorted_json_object& source) + { + for (auto it = source.begin(); it != source.end(); ++it) + { + hint = try_emplace(hint, (*it).key(),(*it).value()); + } + } + + void merge(iterator hint, sorted_json_object&& source) + { + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + iterator pos; + if (hint != members_.end() && hint->key() <= (*it).key()) + { + pos = std::lower_bound(hint,members_.end(), (*it).key(), + Comp()); + } + else + { + pos = std::lower_bound(members_.begin(),members_.end(), (*it).key(), + Comp()); + } + if (pos == members_.end() ) + { + members_.emplace_back(*it); + hint = members_.begin() + (members_.size() - 1); + } + else if ((*it).key() != pos->key()) + { + hint = members_.emplace(pos,*it); + } + } + } + + // merge_or_update + + void merge_or_update(const sorted_json_object& source) + { + for (auto it = source.begin(); it != source.end(); ++it) + { + insert_or_assign((*it).key(),(*it).value()); + } + } + + void merge_or_update(sorted_json_object&& source) + { + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + auto pos = std::lower_bound(members_.begin(),members_.end(), (*it).key(), + Comp()); + if (pos == members_.end() ) + { + members_.emplace_back(*it); + } + else + { + pos->value((*it).value()); + } + } + } + + void merge_or_update(iterator hint, const sorted_json_object& source) + { + for (auto it = source.begin(); it != source.end(); ++it) + { + hint = insert_or_assign(hint, (*it).key(),(*it).value()); + } + } + + void merge_or_update(iterator hint, sorted_json_object&& source) + { + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + iterator pos; + if (hint != members_.end() && hint->key() <= (*it).key()) + { + pos = std::lower_bound(hint,members_.end(), (*it).key(), + Comp()); + } + else + { + pos = std::lower_bound(members_.begin(),members_.end(), (*it).key(), + Comp()); + } + if (pos == members_.end() ) + { + members_.emplace_back(*it); + hint = members_.begin() + (members_.size() - 1); + } + else + { + pos->value((*it).value()); + hint = pos; + } + } + } + + bool operator==(const sorted_json_object& rhs) const + { + return members_ == rhs.members_; + } + + bool operator<(const sorted_json_object& rhs) const + { + return members_ < rhs.members_; + } + private: + + void flatten_and_destroy() noexcept + { + if (!members_.empty()) + { + json_array temp(get_allocator()); + + for (auto& kv : members_) + { + switch (kv.value().storage_kind()) + { + case json_storage_kind::array: + case json_storage_kind::object: + if (!kv.value().empty()) + { + temp.emplace_back(std::move(kv.value())); + } + break; + default: + break; + } + } + } + } + }; + + // Preserve order + template class SequenceContainer = std::vector> + class order_preserving_json_object : public allocator_holder + { + public: + using allocator_type = typename Json::allocator_type; + using char_type = typename Json::char_type; + using key_type = KeyT; + using string_view_type = typename Json::string_view_type; + using key_value_type = key_value; + private: + struct MyHash + { + std::uintmax_t operator()(const key_type& s) const noexcept + { + const int p = 31; + const int m = static_cast(1e9) + 9; + std::uintmax_t hash_value = 0; + std::uintmax_t p_pow = 1; + for (char_type c : s) { + hash_value = (hash_value + (c - 'a' + 1) * p_pow) % m; + p_pow = (p_pow * p) % m; + } + return hash_value; + } + }; + + using key_value_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using key_value_container_type = SequenceContainer; + + key_value_container_type members_; + + struct Comp + { + const key_value_container_type& members_; + + Comp(const key_value_container_type& members_) + : members_(members_) + { + } + + bool operator() (std::size_t i, string_view_type k) const { return members_.at(i).key() < k; } + bool operator() (string_view_type k, std::size_t i) const { return k < members_.at(i).key(); } + }; + public: + using iterator = typename key_value_container_type::iterator; + using const_iterator = typename key_value_container_type::const_iterator; + + using allocator_holder::get_allocator; + + order_preserving_json_object() + { + } + order_preserving_json_object(const allocator_type& alloc) + : allocator_holder(alloc), + members_(key_value_allocator_type(alloc)) + { + } + + order_preserving_json_object(const order_preserving_json_object& val) + : allocator_holder(val.get_allocator()), + members_(val.members_) + { + } + + order_preserving_json_object(order_preserving_json_object&& val,const allocator_type& alloc) + : allocator_holder(alloc), + members_(std::move(val.members_),key_value_allocator_type(alloc)) + { + } + + order_preserving_json_object(order_preserving_json_object&& val) noexcept + : allocator_holder(val.get_allocator()), + members_(std::move(val.members_)) + { + } + + order_preserving_json_object(const order_preserving_json_object& val, const allocator_type& alloc) + : allocator_holder(alloc), + members_(val.members_,key_value_allocator_type(alloc)) + { + } + + template + order_preserving_json_object(InputIt first, InputIt last) + { + std::unordered_set keys; + for (auto it = first; it != last; ++it) + { + auto kv = get_key_value()(*it); + if (keys.find(kv.key()) == keys.end()) + { + keys.emplace(kv.key()); + members_.emplace_back(std::move(kv)); + } + } + } + + template + order_preserving_json_object(InputIt first, InputIt last, + const allocator_type& alloc) + : allocator_holder(alloc), + members_(key_value_allocator_type(alloc)) + { + std::unordered_set keys; + for (auto it = first; it != last; ++it) + { + auto kv = get_key_value()(*it); + if (keys.find(kv.key()) == keys.end()) + { + keys.emplace(kv.key()); + members_.emplace_back(std::move(kv)); + } + } + } + + order_preserving_json_object(std::initializer_list,Json>> init, + const allocator_type& alloc = allocator_type()) + : allocator_holder(alloc), + members_(key_value_allocator_type(alloc)) + { + members_.reserve(init.size()); + for (auto& item : init) + { + insert_or_assign(item.first, item.second); + } + } + + ~order_preserving_json_object() noexcept + { + flatten_and_destroy(); + } + + order_preserving_json_object& operator=(order_preserving_json_object&& val) + { + val.swap(*this); + return *this; + } + + order_preserving_json_object& operator=(const order_preserving_json_object& val) + { + allocator_holder::operator=(val.get_allocator()); + members_ = val.members_; + return *this; + } + + void swap(order_preserving_json_object& other) noexcept + { + members_.swap(other.members_); + } + + bool empty() const + { + return members_.empty(); + } + + iterator begin() + { + return members_.begin(); + } + + iterator end() + { + return members_.end(); + } + + const_iterator begin() const + { + return members_.begin(); + } + + const_iterator end() const + { + return members_.end(); + } + + std::size_t size() const {return members_.size();} + + std::size_t capacity() const {return members_.capacity();} + + void clear() + { + members_.clear(); + } + + void shrink_to_fit() + { + for (std::size_t i = 0; i < members_.size(); ++i) + { + members_[i].shrink_to_fit(); + } + members_.shrink_to_fit(); + } + + void reserve(std::size_t n) {members_.reserve(n);} + + Json& at(std::size_t i) + { + if (i >= members_.size()) + { + JSONCONS_THROW(json_runtime_error("Invalid array subscript")); + } + return members_[i].value(); + } + + const Json& at(std::size_t i) const + { + if (i >= members_.size()) + { + JSONCONS_THROW(json_runtime_error("Invalid array subscript")); + } + return members_[i].value(); + } + + iterator find(const string_view_type& name) noexcept + { + bool found = false; + auto it = members_.begin(); + while (!found && it != members_.end()) + { + if (it->key() == name) + { + found = true; + } + else + { + ++it; + } + } + return it; + } + + const_iterator find(const string_view_type& name) const noexcept + { + bool found = false; + auto it = members_.begin(); + while (!found && it != members_.end()) + { + if (it->key() == name) + { + found = true; + } + else + { + ++it; + } + } + return it; + } + + iterator erase(const_iterator pos) + { + if (pos != members_.end()) + { + return members_.erase(pos); + } + else + { + return members_.end(); + } + } + + iterator erase(const_iterator first, const_iterator last) + { + std::size_t pos1 = first == members_.end() ? members_.size() : first - members_.begin(); + std::size_t pos2 = last == members_.end() ? members_.size() : last - members_.begin(); + + if (pos1 < members_.size() && pos2 <= members_.size()) + { + + return members_.erase(first,last); + } + else + { + return members_.end(); + } + } + + void erase(const string_view_type& name) + { + auto pos = find(name); + if (pos != members_.end()) + { + members_.erase(pos); + } + } + + static bool compare1(const index_key_value& item1, const index_key_value& item2) + { + int comp = item1.name.compare(item2.name); + if (comp < 0) return true; + if (comp == 0) return item1.index < item2.index; + + return false; + } + + static bool compare2(const index_key_value& item1, const index_key_value& item2) + { + return item1.index < item2.index; + } + + void uninitialized_init(index_key_value* items, std::size_t length) + { + if (length > 0) + { + auto first = items; + auto last = first + length; + + std::sort(first, last, compare1); + + std::size_t count = 1; + for (std::size_t i = 1; i < length; ++i) + { + while (i < length && items[i-1].name == items[i].name) + { + ++i; + } + if (i < length) + { + items[count] = items[i]; + ++count; + } + } + + last = first+count; + std::sort(first, last, compare2); + + members_.reserve(count); + for (auto it = first; it != last; ++it) + { + members_.emplace_back(std::move(it->name), std::move(it->value)); + } + } + } + + template + void insert(InputIt first, InputIt last) + { + std::unordered_set keys; + for (auto it = first; it != last; ++it) + { + key_type key{it->first.c_str(), it->first.size(), get_allocator()}; + if (keys.find(key) == keys.end()) + { + keys.emplace(key.c_str(), key.size(), get_allocator()); + members_.emplace_back(std::move(key), it->second); + } + } + } + + template + void insert(sorted_unique_range_tag, InputIt first, InputIt last) + { + for (auto it = first; it != last; ++it) + { + members_.emplace_back(get_key_value()(*it)); + } + } + + template + typename std::enable_if::value,std::pair>::type + insert_or_assign(const string_view_type& name, T&& value) + { + auto it = find(name); + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(), name.end()), std::forward(value)); + auto pos = members_.begin() + (members_.size() - 1); + return std::make_pair(pos, true); + } + else + { + it->value(Json(std::forward(value))); + return std::make_pair(it,false); + } + } + + template + typename std::enable_if::value,std::pair>::type + insert_or_assign(const string_view_type& name, T&& value) + { + auto it = find(name); + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(),name.end(),get_allocator()), std::forward(value)); + auto pos = members_.begin() + (members_.size()-1); + return std::make_pair(pos,true); + } + else + { + it->value(Json(std::forward(value),get_allocator())); + return std::make_pair(it,false); + } + } + + template + typename std::enable_if::value,iterator>::type + insert_or_assign(iterator hint, const string_view_type& key, T&& value) + { + if (hint == members_.end()) + { + auto result = insert_or_assign(key, std::forward(value)); + return result.first; + } + else + { + auto it = find(hint, key); + if (it == members_.end()) + { + members_.emplace_back(key_type(key.begin(), key.end()), std::forward(value)); + auto pos = members_.begin() + (members_.size() - 1); + return pos; + } + else + { + it->value(Json(std::forward(value))); + return it; + } + } + } + + template + typename std::enable_if::value,iterator>::type + insert_or_assign(iterator hint, const string_view_type& key, T&& value) + { + if (hint == members_.end()) + { + auto result = insert_or_assign(key, std::forward(value)); + return result.first; + } + else + { + auto it = find(hint, key); + if (it == members_.end()) + { + members_.emplace_back(key_type(key.begin(),key.end(),get_allocator()), std::forward(value)); + auto pos = members_.begin() + (members_.size()-1); + return pos; + } + else + { + it->value(Json(std::forward(value),get_allocator())); + return it; + } + } + } + + // merge + + void merge(const order_preserving_json_object& source) + { + for (auto it = source.begin(); it != source.end(); ++it) + { + try_emplace((*it).key(),(*it).value()); + } + } + + void merge(order_preserving_json_object&& source) + { + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + auto pos = find((*it).key()); + if (pos == members_.end() ) + { + try_emplace((*it).key(),std::move((*it).value())); + } + } + } + + void merge(iterator hint, const order_preserving_json_object& source) + { + std::size_t pos = hint - members_.begin(); + for (auto it = source.begin(); it != source.end(); ++it) + { + hint = try_emplace(hint, (*it).key(),(*it).value()); + std::size_t newpos = hint - members_.begin(); + if (newpos == pos) + { + ++hint; + pos = hint - members_.begin(); + } + else + { + hint = members_.begin() + pos; + } + } + } + + void merge(iterator hint, order_preserving_json_object&& source) + { + std::size_t pos = hint - members_.begin(); + + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + hint = try_emplace(hint, (*it).key(), std::move((*it).value())); + std::size_t newpos = hint - members_.begin(); + if (newpos == pos) + { + ++hint; + pos = hint - members_.begin(); + } + else + { + hint = members_.begin() + pos; + } + } + } + + // merge_or_update + + void merge_or_update(const order_preserving_json_object& source) + { + for (auto it = source.begin(); it != source.end(); ++it) + { + insert_or_assign((*it).key(),(*it).value()); + } + } + + void merge_or_update(order_preserving_json_object&& source) + { + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + auto pos = find((*it).key()); + if (pos == members_.end() ) + { + insert_or_assign((*it).key(),std::move((*it).value())); + } + else + { + pos->value(std::move((*it).value())); + } + } + } + + void merge_or_update(iterator hint, const order_preserving_json_object& source) + { + std::size_t pos = hint - members_.begin(); + for (auto it = source.begin(); it != source.end(); ++it) + { + hint = insert_or_assign(hint, (*it).key(),(*it).value()); + std::size_t newpos = hint - members_.begin(); + if (newpos == pos) + { + ++hint; + pos = hint - members_.begin(); + } + else + { + hint = members_.begin() + pos; + } + } + } + + void merge_or_update(iterator hint, order_preserving_json_object&& source) + { + std::size_t pos = hint - members_.begin(); + auto it = std::make_move_iterator(source.begin()); + auto end = std::make_move_iterator(source.end()); + for (; it != end; ++it) + { + hint = insert_or_assign(hint, (*it).key(), std::move((*it).value())); + std::size_t newpos = hint - members_.begin(); + if (newpos == pos) + { + ++hint; + pos = hint - members_.begin(); + } + else + { + hint = members_.begin() + pos; + } + } + } + + // try_emplace + + template + typename std::enable_if::value,std::pair>::type + try_emplace(const string_view_type& name, Args&&... args) + { + auto it = find(name); + if (it == members_.end()) + { + members_.emplace_back(key_type(name.begin(), name.end()), std::forward(args)...); + auto pos = members_.begin() + (members_.size()-1); + return std::make_pair(pos,true); + } + else + { + return std::make_pair(it,false); + } + } + + template + typename std::enable_if::value,std::pair>::type + try_emplace(const string_view_type& key, Args&&... args) + { + auto it = find(key); + if (it == members_.end()) + { + members_.emplace_back(key_type(key.begin(),key.end(), get_allocator()), + std::forward(args)...); + auto pos = members_.begin() + members_.size(); + return std::make_pair(pos,true); + } + else + { + return std::make_pair(it,false); + } + } + + template + typename std::enable_if::value,iterator>::type + try_emplace(iterator hint, const string_view_type& key, Args&&... args) + { + if (hint == members_.end()) + { + auto result = try_emplace(key, std::forward(args)...); + return result.first; + } + else + { + auto it = find(hint, key); + if (it == members_.end()) + { + members_.emplace_back(key_type(key.begin(),key.end(), get_allocator()), + std::forward(args)...); + auto pos = members_.begin() + members_.size(); + return pos; + } + else + { + return it; + } + } + } + + template + typename std::enable_if::value,iterator>::type + try_emplace(iterator hint, const string_view_type& key, Args&&... args) + { + if (hint == members_.end()) + { + auto result = try_emplace(key, std::forward(args)...); + return result.first; + } + else + { + auto it = find(hint, key); + if (it == members_.end()) + { + members_.emplace_back(key_type(key.begin(),key.end(), get_allocator()), + std::forward(args)...); + auto pos = members_.begin() + members_.size(); + return pos; + } + else + { + return it; + } + } + } + + bool operator==(const order_preserving_json_object& rhs) const + { + return members_ == rhs.members_; + } + + bool operator<(const order_preserving_json_object& rhs) const + { + return members_ < rhs.members_; + } + private: + + iterator find(iterator hint, const string_view_type& name) noexcept + { + bool found = false; + auto it = hint; + while (!found && it != members_.end()) + { + if (it->key() == name) + { + found = true; + } + else + { + ++it; + } + } + return found ? it : find(name); + } + + void flatten_and_destroy() noexcept + { + if (!members_.empty()) + { + json_array temp(get_allocator()); + + for (auto& kv : members_) + { + switch (kv.value().storage_kind()) + { + case json_storage_kind::array: + case json_storage_kind::object: + if (!kv.value().empty()) + { + temp.emplace_back(std::move(kv.value())); + } + break; + default: + break; + } + } + } + } + }; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/json_options.hpp b/third_party/jsoncons/json_options.hpp new file mode 100644 index 0000000000..dbc48c83b3 --- /dev/null +++ b/third_party/jsoncons/json_options.hpp @@ -0,0 +1,689 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_OPTIONS_HPP +#define JSONCONS_JSON_OPTIONS_HPP + +#include +#include // std::numeric_limits +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + +enum class float_chars_format : uint8_t {general,fixed,scientific,hex}; + +enum class indenting : uint8_t {no_indent = 0, indent = 1}; + +enum class line_split_kind : uint8_t {same_line=1, new_line, multi_line}; + +enum class bigint_chars_format : uint8_t {number, base10, base64, base64url +}; + +enum class byte_string_chars_format : uint8_t {none=0,base16,base64,base64url}; + +enum class spaces_option : uint8_t {no_spaces=0,space_after,space_before,space_before_and_after}; + + +struct default_json_parsing +{ + bool operator()(json_errc ec, const ser_context&) noexcept + { + return ec == json_errc::illegal_comment; + } +}; + +struct strict_json_parsing +{ + bool operator()(json_errc, const ser_context&) noexcept + { + return false; + } +}; + +struct allow_trailing_commas +{ + bool operator()(const std::error_code& ec, const ser_context&) noexcept + { + return ec == json_errc::illegal_comment || ec == jsoncons::json_errc::extra_comma; + } +}; + +template +class basic_json_options; + +template +class basic_json_options_common +{ + friend class basic_json_options; +public: + using char_type = CharT; + using string_type = std::basic_string; +private: + + bool enable_nan_to_num_:1; + bool enable_inf_to_num_:1; + bool enable_neginf_to_num_:1; + bool enable_nan_to_str_:1; + bool enable_inf_to_str_:1; + bool enable_neginf_to_str_:1; + bool enable_str_to_nan_:1; + bool enable_str_to_inf_:1; + bool enable_str_to_neginf_:1; + + string_type nan_to_num_; + string_type inf_to_num_; + string_type neginf_to_num_; + string_type nan_to_str_; + string_type inf_to_str_; + string_type neginf_to_str_; + int max_nesting_depth_; + +protected: + basic_json_options_common() + : + enable_nan_to_num_(false), + enable_inf_to_num_(false), + enable_neginf_to_num_(false), + enable_nan_to_str_(false), + enable_inf_to_str_(false), + enable_neginf_to_str_(false), + enable_str_to_nan_(false), + enable_str_to_inf_(false), + enable_str_to_neginf_(false), + max_nesting_depth_(1024) + {} + + virtual ~basic_json_options_common() noexcept = default; + + basic_json_options_common(const basic_json_options_common&) = default; + basic_json_options_common& operator=(const basic_json_options_common&) = default; + basic_json_options_common(basic_json_options_common&&) = default; + //basic_json_options_common& operator=(basic_json_options_common&&) = default; + +public: + + bool enable_nan_to_num() const + { + return enable_nan_to_num_; + } + + bool enable_inf_to_num() const + { + return enable_inf_to_num_; + } + + bool enable_neginf_to_num() const + { + return enable_neginf_to_num_ || enable_inf_to_num_; + } + + bool enable_nan_to_str() const + { + return enable_nan_to_str_; + } + + bool enable_str_to_nan() const + { + return enable_str_to_nan_; + } + + bool enable_inf_to_str() const + { + return enable_inf_to_str_; + } + + bool enable_str_to_inf() const + { + return enable_str_to_inf_; + } + + bool enable_neginf_to_str() const + { + return enable_neginf_to_str_ || enable_inf_to_str_; + } + + bool enable_str_to_neginf() const + { + return enable_str_to_neginf_ || enable_str_to_inf_; + } + + string_type nan_to_num() const + { + if (enable_nan_to_num_) + { + return nan_to_num_; + } + else + { + return nan_to_num_; // empty string + } + } + + string_type inf_to_num() const + { + if (enable_inf_to_num_) + { + return inf_to_num_; + } + else + { + return inf_to_num_; // empty string + } + } + + string_type neginf_to_num() const + { + if (enable_neginf_to_num_) + { + return neginf_to_num_; + } + else if (enable_inf_to_num_) + { + string_type s; + s.push_back('-'); + s.append(inf_to_num_); + return s; + } + else + { + return neginf_to_num_; // empty string + } + } + + string_type nan_to_str() const + { + if (enable_nan_to_str_) + { + return nan_to_str_; + } + else + { + return nan_to_str_; // empty string + } + } + + string_type inf_to_str() const + { + if (enable_inf_to_str_) + { + return inf_to_str_; + } + else + { + return inf_to_str_; // empty string + } + } + + string_type neginf_to_str() const + { + if (enable_neginf_to_str_) + { + return neginf_to_str_; + } + else if (enable_inf_to_str_) + { + string_type s; + s.push_back('-'); + s.append(inf_to_str_); + return s; + } + else + { + return neginf_to_str_; // empty string + } + } + + int max_nesting_depth() const + { + return max_nesting_depth_; + } +}; + +template +class basic_json_decode_options : public virtual basic_json_options_common +{ + friend class basic_json_options; + using super_type = basic_json_options_common; +public: + using typename super_type::char_type; + using typename super_type::string_type; +private: + bool lossless_number_; + std::function err_handler_; +public: + basic_json_decode_options() + : lossless_number_(false), err_handler_(default_json_parsing()) + { + } + + basic_json_decode_options(const basic_json_decode_options&) = default; + + basic_json_decode_options(basic_json_decode_options&& other) + : super_type(std::move(other)), lossless_number_(other.lossless_number_), err_handler_(std::move(other.err_handler_)) + { + } +protected: + basic_json_decode_options& operator=(const basic_json_decode_options&) = default; + basic_json_decode_options& operator=(basic_json_decode_options&&) = default; +public: + bool lossless_number() const + { + return lossless_number_; + } + + const std::function& err_handler() const + { + return err_handler_; + } + +}; + +template +class basic_json_encode_options : public virtual basic_json_options_common +{ + friend class basic_json_options; + using super_type = basic_json_options_common; +public: + using typename super_type::char_type; + using typename super_type::string_type; + + static constexpr uint8_t indent_size_default = 4; + static constexpr size_t line_length_limit_default = 120; +private: + bool escape_all_non_ascii_:1; + bool escape_solidus_:1; + bool pad_inside_object_braces_:1; + bool pad_inside_array_brackets_:1; + float_chars_format float_format_; + byte_string_chars_format byte_string_format_; + bigint_chars_format bigint_format_; + line_split_kind line_splits_; + line_split_kind object_object_line_splits_; + line_split_kind object_array_line_splits_; + line_split_kind array_array_line_splits_; + line_split_kind array_object_line_splits_; + spaces_option spaces_around_colon_; + spaces_option spaces_around_comma_; + int8_t precision_; + uint8_t indent_size_; + std::size_t line_length_limit_; + string_type new_line_chars_; +public: + basic_json_encode_options() + : escape_all_non_ascii_(false), + escape_solidus_(false), + pad_inside_object_braces_(false), + pad_inside_array_brackets_(false), + float_format_(float_chars_format::general), + byte_string_format_(byte_string_chars_format::none), + bigint_format_(bigint_chars_format::base10), + line_splits_(line_split_kind::multi_line), + object_object_line_splits_(line_split_kind{}), + object_array_line_splits_(line_split_kind{}), + array_array_line_splits_(line_split_kind{}), + array_object_line_splits_(line_split_kind{}), + spaces_around_colon_(spaces_option::space_after), + spaces_around_comma_(spaces_option::space_after), + precision_(0), + indent_size_(indent_size_default), + line_length_limit_(line_length_limit_default) + { + new_line_chars_.push_back('\n'); + } + + basic_json_encode_options(const basic_json_encode_options&) = default; + + basic_json_encode_options(basic_json_encode_options&& other) + : super_type(std::move(other)), + escape_all_non_ascii_(other.escape_all_non_ascii_), + escape_solidus_(other.escape_solidus_), + pad_inside_object_braces_(other.pad_inside_object_braces_), + pad_inside_array_brackets_(other.pad_inside_array_brackets_), + float_format_(other.float_format_), + byte_string_format_(other.byte_string_format_), + bigint_format_(other.bigint_format_), + line_splits_(other.line_splits_), + object_object_line_splits_(other.object_object_line_splits_), + object_array_line_splits_(other.object_array_line_splits_), + array_array_line_splits_(other.array_array_line_splits_), + array_object_line_splits_(other.array_object_line_splits_), + spaces_around_colon_(other.spaces_around_colon_), + spaces_around_comma_(other.spaces_around_comma_), + precision_(other.precision_), + indent_size_(other.indent_size_), + line_length_limit_(other.line_length_limit_), + new_line_chars_(std::move(other.new_line_chars_)) + { + } +protected: + basic_json_encode_options& operator=(const basic_json_encode_options&) = default; + basic_json_encode_options& operator=(basic_json_encode_options&&) = default; +public: + byte_string_chars_format byte_string_format() const {return byte_string_format_;} + + bigint_chars_format bigint_format() const {return bigint_format_;} + + line_split_kind line_splits() const {return line_splits_;} + + line_split_kind object_object_line_splits() const {return object_object_line_splits_ == line_split_kind{} ? line_splits_ : object_object_line_splits_;} + + line_split_kind array_object_line_splits() const {return array_object_line_splits_ == line_split_kind{} ? line_splits_ : array_object_line_splits_;} + + line_split_kind object_array_line_splits() const {return object_array_line_splits_ == line_split_kind{} ? line_splits_ : object_array_line_splits_;} + + line_split_kind array_array_line_splits() const {return array_array_line_splits_ == line_split_kind{} ? line_splits_ : array_array_line_splits_;} + + uint8_t indent_size() const + { + return indent_size_; + } + + spaces_option spaces_around_colon() const + { + return spaces_around_colon_; + } + + spaces_option spaces_around_comma() const + { + return spaces_around_comma_; + } + + bool pad_inside_object_braces() const + { + return pad_inside_object_braces_; + } + + bool pad_inside_array_brackets() const + { + return pad_inside_array_brackets_; + } + + string_type new_line_chars() const + { + return new_line_chars_; + } + + std::size_t line_length_limit() const + { + return line_length_limit_; + } + + float_chars_format float_format() const + { + return float_format_; + } + + int8_t precision() const + { + return precision_; + } + + bool escape_all_non_ascii() const + { + return escape_all_non_ascii_; + } + + bool escape_solidus() const + { + return escape_solidus_; + } + +}; + +template +class basic_json_options final: public basic_json_decode_options, + public basic_json_encode_options +{ +public: + using char_type = CharT; + using string_type = std::basic_string; + + using basic_json_options_common::max_nesting_depth; + + using basic_json_decode_options::enable_str_to_nan; + using basic_json_decode_options::enable_str_to_inf; + using basic_json_decode_options::enable_str_to_neginf; + using basic_json_decode_options::nan_to_str; + using basic_json_decode_options::inf_to_str; + using basic_json_decode_options::neginf_to_str; + using basic_json_decode_options::nan_to_num; + using basic_json_decode_options::inf_to_num; + using basic_json_decode_options::neginf_to_num; + + using basic_json_decode_options::lossless_number; + using basic_json_decode_options::err_handler; + + using basic_json_encode_options::byte_string_format; + using basic_json_encode_options::bigint_format; + using basic_json_encode_options::line_splits; + using basic_json_encode_options::object_object_line_splits; + using basic_json_encode_options::array_object_line_splits; + using basic_json_encode_options::object_array_line_splits; + using basic_json_encode_options::array_array_line_splits; + using basic_json_encode_options::indent_size; + using basic_json_encode_options::spaces_around_colon; + using basic_json_encode_options::spaces_around_comma; + using basic_json_encode_options::pad_inside_object_braces; + using basic_json_encode_options::pad_inside_array_brackets; + using basic_json_encode_options::new_line_chars; + using basic_json_encode_options::line_length_limit; + using basic_json_encode_options::float_format; + using basic_json_encode_options::precision; + using basic_json_encode_options::escape_all_non_ascii; + using basic_json_encode_options::escape_solidus; +public: + +// Constructors + + basic_json_options() = default; + basic_json_options(const basic_json_options&) = default; + basic_json_options(basic_json_options&&) = default; + basic_json_options& operator=(const basic_json_options&) = default; + basic_json_options& operator=(basic_json_options&&) = default; + + basic_json_options& nan_to_num(const string_type& value) + { + this->enable_nan_to_num_ = true; + this->nan_to_str_.clear(); + this->nan_to_num_ = value; + return *this; + } + + basic_json_options& inf_to_num(const string_type& value) + { + this->enable_inf_to_num_ = true; + this->inf_to_str_.clear(); + this->inf_to_num_ = value; + return *this; + } + + basic_json_options& neginf_to_num(const string_type& value) + { + this->enable_neginf_to_num_ = true; + this->neginf_to_str_.clear(); + this->neginf_to_num_ = value; + return *this; + } + + basic_json_options& nan_to_str(const string_type& value, bool enable_inverse = true) + { + this->enable_nan_to_str_ = true; + this->enable_str_to_nan_ = enable_inverse; + this->nan_to_num_.clear(); + this->nan_to_str_ = value; + return *this; + } + + basic_json_options& inf_to_str(const string_type& value, bool enable_inverse = true) + { + this->enable_inf_to_str_ = true; + this->enable_str_to_inf_ = enable_inverse; + this->inf_to_num_.clear(); + this->inf_to_str_ = value; + return *this; + } + + basic_json_options& neginf_to_str(const string_type& value, bool enable_inverse = true) + { + this->enable_neginf_to_str_ = true; + this->enable_str_to_neginf_ = enable_inverse; + this->neginf_to_num_.clear(); + this->neginf_to_str_ = value; + return *this; + } + + basic_json_options& byte_string_format(byte_string_chars_format value) {this->byte_string_format_ = value; return *this;} + + basic_json_options& bigint_format(bigint_chars_format value) {this->bigint_format_ = value; return *this;} + + basic_json_options& line_splits(line_split_kind value) {this->line_splits_ = value; return *this;} + + basic_json_options& object_object_line_splits(line_split_kind value) {this->object_object_line_splits_ = value; return *this;} + + basic_json_options& array_object_line_splits(line_split_kind value) {this->array_object_line_splits_ = value; return *this;} + + basic_json_options& object_array_line_splits(line_split_kind value) {this->object_array_line_splits_ = value; return *this;} + + basic_json_options& array_array_line_splits(line_split_kind value) {this->array_array_line_splits_ = value; return *this;} + + basic_json_options& indent_size(uint8_t value) + { + this->indent_size_ = value; + return *this; + } + + basic_json_options& spaces_around_colon(spaces_option value) + { + this->spaces_around_colon_ = value; + return *this; + } + + basic_json_options& spaces_around_comma(spaces_option value) + { + this->spaces_around_comma_ = value; + return *this; + } + + basic_json_options& pad_inside_object_braces(bool value) + { + this->pad_inside_object_braces_ = value; + return *this; + } + + basic_json_options& pad_inside_array_brackets(bool value) + { + this->pad_inside_array_brackets_ = value; + return *this; + } + + basic_json_options& new_line_chars(const string_type& value) + { + this->new_line_chars_ = value; + return *this; + } + + basic_json_options& lossless_number(bool value) + { + this->lossless_number_ = value; + return *this; + } + + basic_json_options& err_handler(const std::function& value) + { + this->err_handler_ = value; + return *this; + } + + basic_json_options& line_length_limit(std::size_t value) + { + this->line_length_limit_ = value; + return *this; + } + + basic_json_options& float_format(float_chars_format value) + { + this->float_format_ = value; + return *this; + } + + basic_json_options& precision(int8_t value) + { + this->precision_ = value; + return *this; + } + + basic_json_options& escape_all_non_ascii(bool value) + { + this->escape_all_non_ascii_ = value; + return *this; + } + + basic_json_options& escape_solidus(bool value) + { + this->escape_solidus_ = value; + return *this; + } + + basic_json_options& max_nesting_depth(int value) + { + this->max_nesting_depth_ = value; + return *this; + } + +private: + enum class input_state {initial,begin_quote,character,end_quote,escape,error}; + bool is_string(const string_type& s) const + { + input_state state = input_state::initial; + for (char_type c : s) + { + switch (c) + { + case '\t': case ' ': case '\n': case'\r': + break; + case '\\': + state = input_state::escape; + break; + case '\"': + switch (state) + { + case input_state::initial: + state = input_state::begin_quote; + break; + case input_state::begin_quote: + case input_state::character: + state = input_state::end_quote; + break; + case input_state::end_quote: + state = input_state::error; + break; + case input_state::escape: + state = input_state::character; + break; + default: + state = input_state::character; + break; + } + break; + default: + break; + } + + } + return state == input_state::end_quote; + } +}; + +using json_options = basic_json_options; +using wjson_options = basic_json_options; + +} +#endif diff --git a/third_party/jsoncons/json_parser.hpp b/third_party/jsoncons/json_parser.hpp new file mode 100644 index 0000000000..a7efe13b0d --- /dev/null +++ b/third_party/jsoncons/json_parser.hpp @@ -0,0 +1,2827 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_PARSER_HPP +#define JSONCONS_JSON_PARSER_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::numeric_limits +#include // std::function +#include +#include +#include +#include +#include +#include + +#define JSONCONS_ILLEGAL_CONTROL_CHARACTER \ + case 0x00:case 0x01:case 0x02:case 0x03:case 0x04:case 0x05:case 0x06:case 0x07:case 0x08:case 0x0b: \ + case 0x0c:case 0x0e:case 0x0f:case 0x10:case 0x11:case 0x12:case 0x13:case 0x14:case 0x15:case 0x16: \ + case 0x17:case 0x18:case 0x19:case 0x1a:case 0x1b:case 0x1c:case 0x1d:case 0x1e:case 0x1f + +namespace jsoncons { + +namespace detail { + +} + +enum class json_parse_state : uint8_t +{ + root, + start, + accept, + slash, + slash_slash, + slash_star, + slash_star_star, + expect_comma_or_end, + object, + expect_member_name_or_end, + expect_member_name, + expect_colon, + expect_value_or_end, + expect_value, + array, + string, + member_name, + escape, + escape_u1, + escape_u2, + escape_u3, + escape_u4, + escape_expect_surrogate_pair1, + escape_expect_surrogate_pair2, + escape_u5, + escape_u6, + escape_u7, + escape_u8, + minus, + zero, + integer, + fraction1, + fraction2, + exp1, + exp2, + exp3, + n, + nu, + nul, + t, + tr, + tru, + f, + fa, + fal, + fals, + cr, + done +}; + +template > +class basic_json_parser : public ser_context +{ +public: + using char_type = CharT; + using string_view_type = typename basic_json_visitor::string_view_type; +private: + struct string_maps_to_double + { + string_view_type s; + + bool operator()(const std::pair& val) const + { + return val.first == s; + } + }; + + using temp_allocator_type = TempAllocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using parse_state_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + static constexpr std::size_t initial_string_buffer_capacity = 256; + static constexpr int default_initial_stack_capacity = 66; + + basic_json_decode_options options_; + + std::function err_handler_; + int nesting_depth_; + uint32_t cp_; + uint32_t cp2_; + std::size_t line_; + std::size_t position_; + std::size_t mark_position_; + std::size_t saved_position_; + const char_type* begin_input_; + const char_type* end_input_; + const char_type* input_ptr_; + json_parse_state state_; + bool more_; + bool done_; + + std::basic_string,char_allocator_type> string_buffer_; + jsoncons::detail::chars_to to_double_; + + std::vector state_stack_; + std::vector,double>> string_double_map_; + + // Noncopyable and nonmoveable + basic_json_parser(const basic_json_parser&) = delete; + basic_json_parser& operator=(const basic_json_parser&) = delete; + +public: + basic_json_parser(const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_parser(basic_json_decode_options(), default_json_parsing(), temp_alloc) + { + } + + basic_json_parser(std::function err_handler, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_parser(basic_json_decode_options(), err_handler, temp_alloc) + { + } + + basic_json_parser(const basic_json_decode_options& options, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_parser(options, options.err_handler(), temp_alloc) + { + } + + basic_json_parser(const basic_json_decode_options& options, + std::function err_handler, + const TempAllocator& temp_alloc = TempAllocator()) + : options_(options), + err_handler_(err_handler), + nesting_depth_(0), + cp_(0), + cp2_(0), + line_(1), + position_(0), + mark_position_(0), + saved_position_(0), + begin_input_(nullptr), + end_input_(nullptr), + input_ptr_(nullptr), + state_(json_parse_state::start), + more_(true), + done_(false), + string_buffer_(temp_alloc), + state_stack_(temp_alloc) + { + string_buffer_.reserve(initial_string_buffer_capacity); + + std::size_t initial_stack_capacity = (options.max_nesting_depth()+2) <= default_initial_stack_capacity ? (options.max_nesting_depth()+2) : default_initial_stack_capacity; + state_stack_.reserve(initial_stack_capacity ); + push_state(json_parse_state::root); + + if (options_.enable_str_to_nan()) + { + string_double_map_.emplace_back(options_.nan_to_str(),std::nan("")); + } + if (options_.enable_str_to_inf()) + { + string_double_map_.emplace_back(options_.inf_to_str(),std::numeric_limits::infinity()); + } + if (options_.enable_str_to_neginf()) + { + string_double_map_.emplace_back(options_.neginf_to_str(),-std::numeric_limits::infinity()); + } + } + + bool source_exhausted() const + { + return input_ptr_ == end_input_; + } + + ~basic_json_parser() noexcept + { + } + + json_parse_state parent() const + { + JSONCONS_ASSERT(state_stack_.size() >= 1); + return state_stack_.back(); + } + + bool done() const + { + return done_; + } + + bool enter() const + { + return state_ == json_parse_state::start; + } + + bool accept() const + { + return state_ == json_parse_state::accept || done_; + } + + bool stopped() const + { + return !more_; + } + + json_parse_state state() const + { + return state_; + } + + bool finished() const + { + return !more_ && state_ != json_parse_state::accept; + } + + const char_type* first() const + { + return begin_input_; + } + + const char_type* current() const + { + return input_ptr_; + } + + const char_type* last() const + { + return end_input_; + } + + void skip_space() + { + const char_type* local_input_end = end_input_; + while (input_ptr_ != local_input_end) + { + switch (*input_ptr_) + { + case ' ': + case '\t': + ++input_ptr_; + ++position_; + break; + case '\r': + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::cr; + return; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + return; + default: + return; + } + } + } + + void skip_whitespace() + { + const char_type* local_input_end = end_input_; + + while (input_ptr_ != local_input_end) + { + switch (state_) + { + case json_parse_state::cr: + ++line_; + ++position_; + mark_position_ = position_; + switch (*input_ptr_) + { + case '\n': + ++input_ptr_; + ++position_; + state_ = pop_state(); + break; + default: + state_ = pop_state(); + break; + } + break; + + default: + switch (*input_ptr_) + { + case ' ': + case '\t': + case '\n': + case '\r': + skip_space(); + break; + default: + return; + } + break; + } + } + } + + void begin_object(basic_json_visitor& visitor, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + more_ = err_handler_(json_errc::max_nesting_depth_exceeded, *this); + if (!more_) + { + ec = json_errc::max_nesting_depth_exceeded; + return; + } + } + + push_state(json_parse_state::object); + state_ = json_parse_state::expect_member_name_or_end; + more_ = visitor.begin_object(semantic_tag::none, *this, ec); + } + + void end_object(basic_json_visitor& visitor, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(nesting_depth_ < 1)) + { + err_handler_(json_errc::unexpected_rbrace, *this); + ec = json_errc::unexpected_rbrace; + more_ = false; + return; + } + --nesting_depth_; + state_ = pop_state(); + if (state_ == json_parse_state::object) + { + more_ = visitor.end_object(*this, ec); + } + else if (state_ == json_parse_state::array) + { + err_handler_(json_errc::expected_comma_or_rbracket, *this); + ec = json_errc::expected_comma_or_rbracket; + more_ = false; + return; + } + else + { + err_handler_(json_errc::unexpected_rbrace, *this); + ec = json_errc::unexpected_rbrace; + more_ = false; + return; + } + + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + } + + void begin_array(basic_json_visitor& visitor, std::error_code& ec) + { + if (++nesting_depth_ > options_.max_nesting_depth()) + { + more_ = err_handler_(json_errc::max_nesting_depth_exceeded, *this); + if (!more_) + { + ec = json_errc::max_nesting_depth_exceeded; + return; + } + } + + push_state(json_parse_state::array); + state_ = json_parse_state::expect_value_or_end; + more_ = visitor.begin_array(semantic_tag::none, *this, ec); + } + + void end_array(basic_json_visitor& visitor, std::error_code& ec) + { + if (nesting_depth_ < 1) + { + err_handler_(json_errc::unexpected_rbracket, *this); + ec = json_errc::unexpected_rbracket; + more_ = false; + return; + } + --nesting_depth_; + state_ = pop_state(); + if (state_ == json_parse_state::array) + { + more_ = visitor.end_array(*this, ec); + } + else if (state_ == json_parse_state::object) + { + err_handler_(json_errc::expected_comma_or_rbrace, *this); + ec = json_errc::expected_comma_or_rbrace; + more_ = false; + return; + } + else + { + err_handler_(json_errc::unexpected_rbracket, *this); + ec = json_errc::unexpected_rbracket; + more_ = false; + return; + } + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + } + + void reinitialize() + { + reset(); + cp_ = 0; + cp2_ = 0; + saved_position_ = 0; + begin_input_ = nullptr; + end_input_ = nullptr; + input_ptr_ = nullptr; + string_buffer_.clear(); + } + + void reset() + { + state_stack_.clear(); + push_state(json_parse_state::root); + state_ = json_parse_state::start; + more_ = true; + done_ = false; + line_ = 1; + position_ = 0; + mark_position_ = 0; + nesting_depth_ = 0; + } + + void restart() + { + more_ = true; + } + + void check_done() + { + std::error_code ec; + check_done(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line_,column())); + } + } + + void check_done(std::error_code& ec) + { + for (; input_ptr_ != end_input_; ++input_ptr_) + { + char_type curr_char_ = *input_ptr_; + switch (curr_char_) + { + case '\n': + case '\r': + case '\t': + case ' ': + break; + default: + more_ = err_handler_(json_errc::extra_character, *this); + if (!more_) + { + ec = json_errc::extra_character; + return; + } + break; + } + } + } + + void update(const string_view_type sv) + { + update(sv.data(),sv.length()); + } + + void update(const char_type* data, std::size_t length) + { + begin_input_ = data; + end_input_ = data + length; + input_ptr_ = begin_input_; + } + + void parse_some(basic_json_visitor& visitor) + { + std::error_code ec; + parse_some(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line_,column())); + } + } + + void parse_some(basic_json_visitor& visitor, std::error_code& ec) + { + parse_some_(visitor, ec); + } + + void finish_parse(basic_json_visitor& visitor) + { + std::error_code ec; + finish_parse(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line_,column())); + } + } + + void finish_parse(basic_json_visitor& visitor, std::error_code& ec) + { + while (!finished()) + { + parse_some(visitor, ec); + } + } + + void parse_some_(basic_json_visitor& visitor, std::error_code& ec) + { + if (state_ == json_parse_state::accept) + { + visitor.flush(); + done_ = true; + state_ = json_parse_state::done; + more_ = false; + return; + } + const char_type* local_input_end = end_input_; + + if (input_ptr_ == local_input_end && more_) + { + switch (state_) + { + case json_parse_state::zero: + case json_parse_state::integer: + end_integer_value(visitor, ec); + if (ec) return; + break; + case json_parse_state::fraction2: + case json_parse_state::exp3: + end_fraction_value(visitor, ec); + if (ec) return; + break; + case json_parse_state::accept: + visitor.flush(); + done_ = true; + state_ = json_parse_state::done; + more_ = false; + break; + case json_parse_state::start: + case json_parse_state::done: + more_ = false; + break; + case json_parse_state::cr: + state_ = pop_state(); + break; + default: + err_handler_(json_errc::unexpected_eof, *this); + ec = json_errc::unexpected_eof; + more_ = false; + return; + } + } + + while ((input_ptr_ < local_input_end) && more_) + { + switch (state_) + { + case json_parse_state::accept: + visitor.flush(); + done_ = true; + state_ = json_parse_state::done; + more_ = false; + break; + case json_parse_state::cr: + ++line_; + mark_position_ = position_; + switch (*input_ptr_) + { + case '\n': + ++input_ptr_; + ++position_; + state_ = pop_state(); + break; + default: + state_ = pop_state(); + break; + } + break; + case json_parse_state::start: + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + return; + } + break; + case '\r': + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::cr; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case ' ':case '\t': + skip_space(); + break; + case '/': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::slash; + break; + case '{': + saved_position_ = position_; + ++input_ptr_; + ++position_; + begin_object(visitor, ec); + if (ec) return; + break; + case '[': + saved_position_ = position_; + ++input_ptr_; + ++position_; + begin_array(visitor, ec); + if (ec) return; + break; + case '\"': + state_ = json_parse_state::string; + saved_position_ = position_; + ++input_ptr_; + ++position_; + string_buffer_.clear(); + parse_string(visitor, ec); + if (ec) return; + break; + case '-': + string_buffer_.clear(); + string_buffer_.push_back('-'); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::minus; + parse_number(visitor, ec); + if (ec) {return;} + break; + case '0': + string_buffer_.clear(); + string_buffer_.push_back(static_cast(*input_ptr_)); + state_ = json_parse_state::zero; + saved_position_ = position_; + ++input_ptr_; + ++position_; + parse_number(visitor, ec); + if (ec) {return;} + break; + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.clear(); + string_buffer_.push_back(static_cast(*input_ptr_)); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::integer; + parse_number(visitor, ec); + if (ec) {return;} + break; + case 'n': + parse_null(visitor, ec); + if (ec) {return;} + break; + case 't': + parse_true(visitor, ec); + if (ec) {return;} + break; + case 'f': + parse_false(visitor, ec); + if (ec) {return;} + break; + case '}': + err_handler_(json_errc::unexpected_rbrace, *this); + ec = json_errc::unexpected_rbrace; + more_ = false; + return; + case ']': + err_handler_(json_errc::unexpected_rbracket, *this); + ec = json_errc::unexpected_rbracket; + more_ = false; + return; + default: + err_handler_(json_errc::syntax_error, *this); + ec = json_errc::syntax_error; + more_ = false; + return; + } + } + break; + + case json_parse_state::expect_comma_or_end: + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + return; + } + ++input_ptr_; + ++position_; + break; + case '\r': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::cr; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case ' ':case '\t': + skip_space(); + break; + case '/': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::slash; + break; + case '}': + saved_position_ = position_; + ++input_ptr_; + ++position_; + end_object(visitor, ec); + if (ec) return; + break; + case ']': + saved_position_ = position_; + ++input_ptr_; + ++position_; + end_array(visitor, ec); + if (ec) return; + break; + case ',': + begin_member_or_element(ec); + if (ec) return; + ++input_ptr_; + ++position_; + break; + default: + if (parent() == json_parse_state::array) + { + more_ = err_handler_(json_errc::expected_comma_or_rbracket, *this); + if (!more_) + { + ec = json_errc::expected_comma_or_rbracket; + return; + } + } + else if (parent() == json_parse_state::object) + { + more_ = err_handler_(json_errc::expected_comma_or_rbrace, *this); + if (!more_) + { + ec = json_errc::expected_comma_or_rbrace; + return; + } + } + ++input_ptr_; + ++position_; + break; + } + } + break; + case json_parse_state::expect_member_name_or_end: + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + return; + } + ++input_ptr_; + ++position_; + break; + case '\r': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::cr; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case ' ':case '\t': + skip_space(); + break; + case '/': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::slash; + break; + case '}': + saved_position_ = position_; + ++input_ptr_; + ++position_; + end_object(visitor, ec); + if (ec) return; + break; + case '\"': + saved_position_ = position_; + ++input_ptr_; + ++position_; + push_state(json_parse_state::member_name); + state_ = json_parse_state::string; + string_buffer_.clear(); + parse_string(visitor, ec); + if (ec) return; + break; + case '\'': + more_ = err_handler_(json_errc::single_quote, *this); + if (!more_) + { + ec = json_errc::single_quote; + return; + } + ++input_ptr_; + ++position_; + break; + default: + more_ = err_handler_(json_errc::expected_key, *this); + if (!more_) + { + ec = json_errc::expected_key; + return; + } + ++input_ptr_; + ++position_; + break; + } + } + break; + case json_parse_state::expect_member_name: + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + return; + } + ++input_ptr_; + ++position_; + break; + case '\r': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::cr; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case ' ':case '\t': + skip_space(); + break; + case '/': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::slash; + break; + case '\"': + saved_position_ = position_; + ++input_ptr_; + ++position_; + push_state(json_parse_state::member_name); + state_ = json_parse_state::string; + string_buffer_.clear(); + parse_string(visitor, ec); + if (ec) return; + break; + case '}': + more_ = err_handler_(json_errc::extra_comma, *this); + if (!more_) + { + ec = json_errc::extra_comma; + return; + } + saved_position_ = position_; + ++input_ptr_; + ++position_; + end_object(visitor, ec); // Recover + if (ec) return; + break; + case '\'': + more_ = err_handler_(json_errc::single_quote, *this); + if (!more_) + { + ec = json_errc::single_quote; + return; + } + ++input_ptr_; + ++position_; + break; + default: + more_ = err_handler_(json_errc::expected_key, *this); + if (!more_) + { + ec = json_errc::expected_key; + return; + } + ++input_ptr_; + ++position_; + break; + } + } + break; + case json_parse_state::expect_colon: + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + return; + } + ++input_ptr_; + ++position_; + break; + case '\r': + push_state(state_); + state_ = json_parse_state::cr; + ++input_ptr_; + ++position_; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case ' ':case '\t': + skip_space(); + break; + case '/': + push_state(state_); + state_ = json_parse_state::slash; + ++input_ptr_; + ++position_; + break; + case ':': + state_ = json_parse_state::expect_value; + ++input_ptr_; + ++position_; + break; + default: + more_ = err_handler_(json_errc::expected_colon, *this); + if (!more_) + { + ec = json_errc::expected_colon; + return; + } + ++input_ptr_; + ++position_; + break; + } + } + break; + + case json_parse_state::expect_value: + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + return; + } + ++input_ptr_; + ++position_; + break; + case '\r': + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::cr; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case ' ':case '\t': + skip_space(); + break; + case '/': + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::slash; + break; + case '{': + saved_position_ = position_; + ++input_ptr_; + ++position_; + begin_object(visitor, ec); + if (ec) return; + break; + case '[': + saved_position_ = position_; + ++input_ptr_; + ++position_; + begin_array(visitor, ec); + if (ec) return; + break; + case '\"': + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::string; + string_buffer_.clear(); + parse_string(visitor, ec); + if (ec) return; + break; + case '-': + string_buffer_.clear(); + string_buffer_.push_back('-'); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::minus; + parse_number(visitor, ec); + if (ec) {return;} + break; + case '0': + string_buffer_.clear(); + string_buffer_.push_back(static_cast(*input_ptr_)); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::zero; + parse_number(visitor, ec); + if (ec) {return;} + break; + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.clear(); + string_buffer_.push_back(static_cast(*input_ptr_)); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::integer; + parse_number(visitor, ec); + if (ec) {return;} + break; + case 'n': + parse_null(visitor, ec); + if (ec) {return;} + break; + case 't': + parse_true(visitor, ec); + if (ec) {return;} + break; + case 'f': + parse_false(visitor, ec); + if (ec) {return;} + break; + case ']': + saved_position_ = position_; + ++input_ptr_; + ++position_; + if (parent() == json_parse_state::array) + { + more_ = err_handler_(json_errc::extra_comma, *this); + if (!more_) + { + ec = json_errc::extra_comma; + return; + } + end_array(visitor, ec); // Recover + if (ec) return; + } + else + { + more_ = err_handler_(json_errc::expected_value, *this); + if (!more_) + { + ec = json_errc::expected_value; + return; + } + } + break; + case '\'': + more_ = err_handler_(json_errc::single_quote, *this); + if (!more_) + { + ec = json_errc::single_quote; + return; + } + ++input_ptr_; + ++position_; + break; + default: + more_ = err_handler_(json_errc::expected_value, *this); + if (!more_) + { + ec = json_errc::expected_value; + return; + } + ++input_ptr_; + ++position_; + break; + } + } + break; + case json_parse_state::expect_value_or_end: + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + return; + } + ++input_ptr_; + ++position_; + break; + case '\r': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::cr; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case ' ':case '\t': + skip_space(); + break; + case '/': + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::slash; + break; + case '{': + saved_position_ = position_; + ++input_ptr_; + ++position_; + begin_object(visitor, ec); + if (ec) return; + break; + case '[': + saved_position_ = position_; + ++input_ptr_; + ++position_; + begin_array(visitor, ec); + if (ec) return; + break; + case ']': + saved_position_ = position_; + ++input_ptr_; + ++position_; + end_array(visitor, ec); + if (ec) return; + break; + case '\"': + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::string; + string_buffer_.clear(); + parse_string(visitor, ec); + if (ec) return; + break; + case '-': + string_buffer_.clear(); + string_buffer_.push_back('-'); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::minus; + parse_number(visitor, ec); + if (ec) {return;} + break; + case '0': + string_buffer_.clear(); + string_buffer_.push_back(static_cast(*input_ptr_)); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::zero; + parse_number(visitor, ec); + if (ec) {return;} + break; + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.clear(); + string_buffer_.push_back(static_cast(*input_ptr_)); + saved_position_ = position_; + ++input_ptr_; + ++position_; + state_ = json_parse_state::integer; + parse_number(visitor, ec); + if (ec) {return;} + break; + case 'n': + parse_null(visitor, ec); + if (ec) {return;} + break; + case 't': + parse_true(visitor, ec); + if (ec) {return;} + break; + case 'f': + parse_false(visitor, ec); + if (ec) {return;} + break; + case '\'': + more_ = err_handler_(json_errc::single_quote, *this); + if (!more_) + { + ec = json_errc::single_quote; + return; + } + ++input_ptr_; + ++position_; + break; + default: + more_ = err_handler_(json_errc::expected_value, *this); + if (!more_) + { + ec = json_errc::expected_value; + return; + } + ++input_ptr_; + ++position_; + break; + } + } + break; + case json_parse_state::string: + case json_parse_state::escape: + case json_parse_state::escape_u1: + case json_parse_state::escape_u2: + case json_parse_state::escape_u3: + case json_parse_state::escape_u4: + case json_parse_state::escape_expect_surrogate_pair1: + case json_parse_state::escape_expect_surrogate_pair2: + case json_parse_state::escape_u5: + case json_parse_state::escape_u6: + case json_parse_state::escape_u7: + case json_parse_state::escape_u8: + parse_string(visitor, ec); + if (ec) return; + break; + case json_parse_state::minus: + case json_parse_state::zero: + case json_parse_state::integer: + case json_parse_state::fraction1: + case json_parse_state::fraction2: + case json_parse_state::exp1: + case json_parse_state::exp2: + case json_parse_state::exp3: + parse_number(visitor, ec); + if (ec) return; + break; + case json_parse_state::t: + switch (*input_ptr_) + { + case 'r': + ++input_ptr_; + ++position_; + state_ = json_parse_state::tr; + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + break; + case json_parse_state::tr: + switch (*input_ptr_) + { + case 'u': + state_ = json_parse_state::tru; + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + ++input_ptr_; + ++position_; + break; + case json_parse_state::tru: + switch (*input_ptr_) + { + case 'e': + ++input_ptr_; + ++position_; + more_ = visitor.bool_value(true, semantic_tag::none, *this, ec); + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + break; + case json_parse_state::f: + switch (*input_ptr_) + { + case 'a': + ++input_ptr_; + ++position_; + state_ = json_parse_state::fa; + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + break; + case json_parse_state::fa: + switch (*input_ptr_) + { + case 'l': + state_ = json_parse_state::fal; + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + ++input_ptr_; + ++position_; + break; + case json_parse_state::fal: + switch (*input_ptr_) + { + case 's': + state_ = json_parse_state::fals; + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + ++input_ptr_; + ++position_; + break; + case json_parse_state::fals: + switch (*input_ptr_) + { + case 'e': + ++input_ptr_; + ++position_; + more_ = visitor.bool_value(false, semantic_tag::none, *this, ec); + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + break; + case json_parse_state::n: + switch (*input_ptr_) + { + case 'u': + ++input_ptr_; + ++position_; + state_ = json_parse_state::nu; + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + break; + case json_parse_state::nu: + switch (*input_ptr_) + { + case 'l': + state_ = json_parse_state::nul; + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + ++input_ptr_; + ++position_; + break; + case json_parse_state::nul: + ++position_; + switch (*input_ptr_) + { + case 'l': + more_ = visitor.null_value(semantic_tag::none, *this, ec); + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + break; + default: + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + ++input_ptr_; + break; + case json_parse_state::slash: + { + switch (*input_ptr_) + { + case '*': + state_ = json_parse_state::slash_star; + more_ = err_handler_(json_errc::illegal_comment, *this); + if (!more_) + { + ec = json_errc::illegal_comment; + return; + } + break; + case '/': + state_ = json_parse_state::slash_slash; + more_ = err_handler_(json_errc::illegal_comment, *this); + if (!more_) + { + ec = json_errc::illegal_comment; + return; + } + break; + default: + more_ = err_handler_(json_errc::syntax_error, *this); + if (!more_) + { + ec = json_errc::syntax_error; + return; + } + break; + } + ++input_ptr_; + ++position_; + break; + } + case json_parse_state::slash_star: + { + switch (*input_ptr_) + { + case '\r': + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::cr; + break; + case '\n': + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + break; + case '*': + ++input_ptr_; + ++position_; + state_ = json_parse_state::slash_star_star; + break; + default: + ++input_ptr_; + ++position_; + break; + } + break; + } + case json_parse_state::slash_slash: + { + switch (*input_ptr_) + { + case '\r': + case '\n': + state_ = pop_state(); + break; + default: + ++input_ptr_; + ++position_; + } + break; + } + case json_parse_state::slash_star_star: + { + switch (*input_ptr_) + { + case '/': + state_ = pop_state(); + break; + default: + state_ = json_parse_state::slash_star; + break; + } + ++input_ptr_; + ++position_; + break; + } + default: + JSONCONS_ASSERT(false); + break; + } + } + } + + void parse_true(basic_json_visitor& visitor, std::error_code& ec) + { + saved_position_ = position_; + if (JSONCONS_LIKELY(end_input_ - input_ptr_ >= 4)) + { + if (*(input_ptr_+1) == 'r' && *(input_ptr_+2) == 'u' && *(input_ptr_+3) == 'e') + { + input_ptr_ += 4; + position_ += 4; + more_ = visitor.bool_value(true, semantic_tag::none, *this, ec); + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + } + else + { + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + } + else + { + ++input_ptr_; + ++position_; + state_ = json_parse_state::t; + } + } + + void parse_null(basic_json_visitor& visitor, std::error_code& ec) + { + saved_position_ = position_; + if (JSONCONS_LIKELY(end_input_ - input_ptr_ >= 4)) + { + if (*(input_ptr_+1) == 'u' && *(input_ptr_+2) == 'l' && *(input_ptr_+3) == 'l') + { + input_ptr_ += 4; + position_ += 4; + more_ = visitor.null_value(semantic_tag::none, *this, ec); + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + } + else + { + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + } + else + { + ++input_ptr_; + ++position_; + state_ = json_parse_state::n; + } + } + + void parse_false(basic_json_visitor& visitor, std::error_code& ec) + { + saved_position_ = position_; + if (JSONCONS_LIKELY(end_input_ - input_ptr_ >= 5)) + { + if (*(input_ptr_+1) == 'a' && *(input_ptr_+2) == 'l' && *(input_ptr_+3) == 's' && *(input_ptr_+4) == 'e') + { + input_ptr_ += 5; + position_ += 5; + more_ = visitor.bool_value(false, semantic_tag::none, *this, ec); + if (parent() == json_parse_state::root) + { + state_ = json_parse_state::accept; + } + else + { + state_ = json_parse_state::expect_comma_or_end; + } + } + else + { + err_handler_(json_errc::invalid_value, *this); + ec = json_errc::invalid_value; + more_ = false; + return; + } + } + else + { + ++input_ptr_; + ++position_; + state_ = json_parse_state::f; + } + } + + void parse_number(basic_json_visitor& visitor, std::error_code& ec) + { + const char_type* local_input_end = end_input_; + + switch (state_) + { + case json_parse_state::minus: + goto minus_sign; + case json_parse_state::zero: + goto zero; + case json_parse_state::integer: + goto integer; + case json_parse_state::fraction1: + goto fraction1; + case json_parse_state::fraction2: + goto fraction2; + case json_parse_state::exp1: + goto exp1; + case json_parse_state::exp2: + goto exp2; + case json_parse_state::exp3: + goto exp3; + default: + JSONCONS_UNREACHABLE(); + } +minus_sign: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::minus; + return; + } + switch (*input_ptr_) + { + case '0': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto zero; + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto integer; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::expected_value; + more_ = false; + return; + } +zero: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::zero; + return; + } + switch (*input_ptr_) + { + case '\r': + end_integer_value(visitor, ec); + if (ec) return; + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::cr; + return; + case '\n': + end_integer_value(visitor, ec); + if (ec) return; + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + return; + case ' ':case '\t': + end_integer_value(visitor, ec); + if (ec) return; + skip_space(); + return; + case '/': + end_integer_value(visitor, ec); + if (ec) return; + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::slash; + return; + case '}': + case ']': + end_integer_value(visitor, ec); + if (ec) return; + state_ = json_parse_state::expect_comma_or_end; + return; + case '.': + string_buffer_.push_back(to_double_.get_decimal_point()); + ++input_ptr_; + ++position_; + goto fraction1; + case 'e':case 'E': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto exp1; + case ',': + end_integer_value(visitor, ec); + if (ec) return; + begin_member_or_element(ec); + if (ec) return; + ++input_ptr_; + ++position_; + return; + case '0': case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + err_handler_(json_errc::leading_zero, *this); + ec = json_errc::leading_zero; + more_ = false; + state_ = json_parse_state::zero; + return; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::invalid_number; + more_ = false; + state_ = json_parse_state::zero; + return; + } +integer: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::integer; + return; + } + switch (*input_ptr_) + { + case '\r': + end_integer_value(visitor, ec); + if (ec) return; + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::cr; + return; + case '\n': + end_integer_value(visitor, ec); + if (ec) return; + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + return; + case ' ':case '\t': + end_integer_value(visitor, ec); + if (ec) return; + skip_space(); + return; + case '/': + end_integer_value(visitor, ec); + if (ec) return; + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::slash; + return; + case '}': + case ']': + end_integer_value(visitor, ec); + if (ec) return; + state_ = json_parse_state::expect_comma_or_end; + return; + case '0': case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto integer; + case '.': + string_buffer_.push_back(to_double_.get_decimal_point()); + ++input_ptr_; + ++position_; + goto fraction1; + case 'e':case 'E': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto exp1; + case ',': + end_integer_value(visitor, ec); + if (ec) return; + begin_member_or_element(ec); + if (ec) return; + ++input_ptr_; + ++position_; + return; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::invalid_number; + more_ = false; + state_ = json_parse_state::integer; + return; + } +fraction1: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::fraction1; + return; + } + switch (*input_ptr_) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto fraction2; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::invalid_number; + more_ = false; + state_ = json_parse_state::fraction1; + return; + } +fraction2: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::fraction2; + return; + } + switch (*input_ptr_) + { + case '\r': + end_fraction_value(visitor, ec); + if (ec) return; + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::cr; + return; + case '\n': + end_fraction_value(visitor, ec); + if (ec) return; + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + return; + case ' ':case '\t': + end_fraction_value(visitor, ec); + if (ec) return; + skip_space(); + return; + case '/': + end_fraction_value(visitor, ec); + if (ec) return; + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::slash; + return; + case '}': + end_fraction_value(visitor, ec); + if (ec) return; + state_ = json_parse_state::expect_comma_or_end; + return; + case ']': + end_fraction_value(visitor, ec); + if (ec) return; + state_ = json_parse_state::expect_comma_or_end; + return; + case ',': + end_fraction_value(visitor, ec); + if (ec) return; + begin_member_or_element(ec); + if (ec) return; + ++input_ptr_; + ++position_; + return; + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto fraction2; + case 'e':case 'E': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto exp1; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::invalid_number; + more_ = false; + state_ = json_parse_state::fraction2; + return; + } +exp1: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::exp1; + return; + } + switch (*input_ptr_) + { + case '+': + ++input_ptr_; + ++position_; + goto exp2; + case '-': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto exp2; + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto exp3; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::expected_value; + more_ = false; + state_ = json_parse_state::exp1; + return; + } +exp2: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::exp2; + return; + } + switch (*input_ptr_) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto exp3; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::expected_value; + more_ = false; + state_ = json_parse_state::exp2; + return; + } + +exp3: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::exp3; + return; + } + switch (*input_ptr_) + { + case '\r': + end_fraction_value(visitor, ec); + if (ec) return; + ++input_ptr_; + ++position_; + push_state(state_); + state_ = json_parse_state::cr; + return; + case '\n': + end_fraction_value(visitor, ec); + if (ec) return; + ++input_ptr_; + ++line_; + ++position_; + mark_position_ = position_; + return; + case ' ':case '\t': + end_fraction_value(visitor, ec); + if (ec) return; + skip_space(); + return; + case '/': + end_fraction_value(visitor, ec); + if (ec) return; + push_state(state_); + ++input_ptr_; + ++position_; + state_ = json_parse_state::slash; + return; + case '}': + end_fraction_value(visitor, ec); + if (ec) return; + state_ = json_parse_state::expect_comma_or_end; + return; + case ']': + end_fraction_value(visitor, ec); + if (ec) return; + state_ = json_parse_state::expect_comma_or_end; + return; + case ',': + end_fraction_value(visitor, ec); + if (ec) return; + begin_member_or_element(ec); + if (ec) return; + ++input_ptr_; + ++position_; + return; + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + string_buffer_.push_back(static_cast(*input_ptr_)); + ++input_ptr_; + ++position_; + goto exp3; + default: + err_handler_(json_errc::invalid_number, *this); + ec = json_errc::invalid_number; + more_ = false; + state_ = json_parse_state::exp3; + return; + } + + JSONCONS_UNREACHABLE(); + } + + void parse_string(basic_json_visitor& visitor, std::error_code& ec) + { + const char_type* local_input_end = end_input_; + const char_type* sb = input_ptr_; + + switch (state_) + { + case json_parse_state::string: + goto string_u1; + case json_parse_state::escape: + goto escape; + case json_parse_state::escape_u1: + goto escape_u1; + case json_parse_state::escape_u2: + goto escape_u2; + case json_parse_state::escape_u3: + goto escape_u3; + case json_parse_state::escape_u4: + goto escape_u4; + case json_parse_state::escape_expect_surrogate_pair1: + goto escape_expect_surrogate_pair1; + case json_parse_state::escape_expect_surrogate_pair2: + goto escape_expect_surrogate_pair2; + case json_parse_state::escape_u5: + goto escape_u5; + case json_parse_state::escape_u6: + goto escape_u6; + case json_parse_state::escape_u7: + goto escape_u7; + case json_parse_state::escape_u8: + goto escape_u8; + default: + JSONCONS_UNREACHABLE(); + } + +string_u1: + while (input_ptr_ < local_input_end) + { + switch (*input_ptr_) + { + JSONCONS_ILLEGAL_CONTROL_CHARACTER: + { + position_ += (input_ptr_ - sb + 1); + more_ = err_handler_(json_errc::illegal_control_character, *this); + if (!more_) + { + ec = json_errc::illegal_control_character; + state_ = json_parse_state::string; + return; + } + // recovery - skip + string_buffer_.append(sb,input_ptr_-sb); + ++input_ptr_; + state_ = json_parse_state::string; + return; + } + case '\r': + { + position_ += (input_ptr_ - sb + 1); + more_ = err_handler_(json_errc::illegal_character_in_string, *this); + if (!more_) + { + ec = json_errc::illegal_character_in_string; + state_ = json_parse_state::string; + return; + } + // recovery - keep + string_buffer_.append(sb, input_ptr_ - sb + 1); + ++input_ptr_; + push_state(state_); + state_ = json_parse_state::cr; + return; + } + case '\n': + { + ++line_; + ++position_; + mark_position_ = position_; + more_ = err_handler_(json_errc::illegal_character_in_string, *this); + if (!more_) + { + ec = json_errc::illegal_character_in_string; + state_ = json_parse_state::string; + return; + } + // recovery - keep + string_buffer_.append(sb, input_ptr_ - sb + 1); + ++input_ptr_; + return; + } + case '\t': + { + position_ += (input_ptr_ - sb + 1); + more_ = err_handler_(json_errc::illegal_character_in_string, *this); + if (!more_) + { + ec = json_errc::illegal_character_in_string; + state_ = json_parse_state::string; + return; + } + // recovery - keep + string_buffer_.append(sb, input_ptr_ - sb + 1); + ++input_ptr_; + state_ = json_parse_state::string; + return; + } + case '\\': + { + string_buffer_.append(sb,input_ptr_-sb); + position_ += (input_ptr_ - sb + 1); + ++input_ptr_; + goto escape; + } + case '\"': + { + position_ += (input_ptr_ - sb + 1); + if (string_buffer_.length() == 0) + { + end_string_value(sb,input_ptr_-sb, visitor, ec); + if (ec) {return;} + } + else + { + string_buffer_.append(sb,input_ptr_-sb); + end_string_value(string_buffer_.data(),string_buffer_.length(), visitor, ec); + if (ec) {return;} + } + ++input_ptr_; + return; + } + default: + break; + } + ++input_ptr_; + } + + // Buffer exhausted + { + string_buffer_.append(sb,input_ptr_-sb); + position_ += (input_ptr_ - sb); + state_ = json_parse_state::string; + return; + } + +escape: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape; + return; + } + switch (*input_ptr_) + { + case '\"': + string_buffer_.push_back('\"'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case '\\': + string_buffer_.push_back('\\'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case '/': + string_buffer_.push_back('/'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case 'b': + string_buffer_.push_back('\b'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case 'f': + string_buffer_.push_back('\f'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case 'n': + string_buffer_.push_back('\n'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case 'r': + string_buffer_.push_back('\r'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case 't': + string_buffer_.push_back('\t'); + sb = ++input_ptr_; + ++position_; + goto string_u1; + case 'u': + cp_ = 0; + ++input_ptr_; + ++position_; + goto escape_u1; + default: + err_handler_(json_errc::illegal_escaped_character, *this); + ec = json_errc::illegal_escaped_character; + more_ = false; + state_ = json_parse_state::escape; + return; + } + +escape_u1: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u1; + return; + } + { + cp_ = append_to_codepoint(0, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u1; + return; + } + ++input_ptr_; + ++position_; + goto escape_u2; + } + +escape_u2: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u2; + return; + } + { + cp_ = append_to_codepoint(cp_, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u2; + return; + } + ++input_ptr_; + ++position_; + goto escape_u3; + } + +escape_u3: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u3; + return; + } + { + cp_ = append_to_codepoint(cp_, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u3; + return; + } + ++input_ptr_; + ++position_; + goto escape_u4; + } + +escape_u4: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u4; + return; + } + { + cp_ = append_to_codepoint(cp_, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u4; + return; + } + if (unicode_traits::is_high_surrogate(cp_)) + { + ++input_ptr_; + ++position_; + goto escape_expect_surrogate_pair1; + } + else + { + unicode_traits::convert(&cp_, 1, string_buffer_); + sb = ++input_ptr_; + ++position_; + state_ = json_parse_state::string; + return; + } + } + +escape_expect_surrogate_pair1: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_expect_surrogate_pair1; + return; + } + { + switch (*input_ptr_) + { + case '\\': + cp2_ = 0; + ++input_ptr_; + ++position_; + goto escape_expect_surrogate_pair2; + default: + err_handler_(json_errc::expected_codepoint_surrogate_pair, *this); + ec = json_errc::expected_codepoint_surrogate_pair; + more_ = false; + state_ = json_parse_state::escape_expect_surrogate_pair1; + return; + } + } + +escape_expect_surrogate_pair2: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_expect_surrogate_pair2; + return; + } + { + switch (*input_ptr_) + { + case 'u': + ++input_ptr_; + ++position_; + goto escape_u5; + default: + err_handler_(json_errc::expected_codepoint_surrogate_pair, *this); + ec = json_errc::expected_codepoint_surrogate_pair; + more_ = false; + state_ = json_parse_state::escape_expect_surrogate_pair2; + return; + } + } + +escape_u5: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u5; + return; + } + { + cp2_ = append_to_codepoint(0, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u5; + return; + } + } + ++input_ptr_; + ++position_; + goto escape_u6; + +escape_u6: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u6; + return; + } + { + cp2_ = append_to_codepoint(cp2_, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u6; + return; + } + ++input_ptr_; + ++position_; + goto escape_u7; + } + +escape_u7: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u7; + return; + } + { + cp2_ = append_to_codepoint(cp2_, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u7; + return; + } + ++input_ptr_; + ++position_; + goto escape_u8; + } + +escape_u8: + if (JSONCONS_UNLIKELY(input_ptr_ >= local_input_end)) // Buffer exhausted + { + state_ = json_parse_state::escape_u8; + return; + } + { + cp2_ = append_to_codepoint(cp2_, *input_ptr_, ec); + if (ec) + { + state_ = json_parse_state::escape_u8; + return; + } + uint32_t cp = 0x10000 + ((cp_ & 0x3FF) << 10) + (cp2_ & 0x3FF); + unicode_traits::convert(&cp, 1, string_buffer_); + sb = ++input_ptr_; + ++position_; + goto string_u1; + } + + JSONCONS_UNREACHABLE(); + } + + void translate_conv_errc(unicode_traits::conv_errc result, std::error_code& ec) + { + switch (result) + { + case unicode_traits::conv_errc(): + break; + case unicode_traits::conv_errc::over_long_utf8_sequence: + more_ = err_handler_(json_errc::over_long_utf8_sequence, *this); + if (!more_) + { + ec = json_errc::over_long_utf8_sequence; + return; + } + break; + case unicode_traits::conv_errc::unpaired_high_surrogate: + more_ = err_handler_(json_errc::unpaired_high_surrogate, *this); + if (!more_) + { + ec = json_errc::unpaired_high_surrogate; + return; + } + break; + case unicode_traits::conv_errc::expected_continuation_byte: + more_ = err_handler_(json_errc::expected_continuation_byte, *this); + if (!more_) + { + ec = json_errc::expected_continuation_byte; + return; + } + break; + case unicode_traits::conv_errc::illegal_surrogate_value: + more_ = err_handler_(json_errc::illegal_surrogate_value, *this); + if (!more_) + { + ec = json_errc::illegal_surrogate_value; + return; + } + break; + default: + more_ = err_handler_(json_errc::illegal_codepoint, *this); + if (!more_) + { + ec = json_errc::illegal_codepoint; + return; + } + break; + } + } + + std::size_t line() const override + { + return line_; + } + + std::size_t column() const override + { + return (position_ - mark_position_) + 1; + } + + std::size_t position() const override + { + return saved_position_; + } + + std::size_t end_position() const override + { + return position_; + } + + std::size_t offset() const + { + return input_ptr_ - begin_input_; + } +private: + + void end_integer_value(basic_json_visitor& visitor, std::error_code& ec) + { + if (string_buffer_[0] == '-') + { + end_negative_value(visitor, ec); + } + else + { + end_positive_value(visitor, ec); + } + } + + void end_negative_value(basic_json_visitor& visitor, std::error_code& ec) + { + int64_t val; + auto result = jsoncons::detail::to_integer_unchecked(string_buffer_.data(), string_buffer_.length(), val); + if (result) + { + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + } + else // Must be overflow + { + more_ = visitor.string_value(string_buffer_, semantic_tag::bigint, *this, ec); + } + after_value(ec); + } + + void end_positive_value(basic_json_visitor& visitor, std::error_code& ec) + { + uint64_t val; + auto result = jsoncons::detail::to_integer_unchecked(string_buffer_.data(), string_buffer_.length(), val); + if (result) + { + more_ = visitor.uint64_value(val, semantic_tag::none, *this, ec); + } + else // Must be overflow + { + more_ = visitor.string_value(string_buffer_, semantic_tag::bigint, *this, ec); + } + after_value(ec); + } + + void end_fraction_value(basic_json_visitor& visitor, std::error_code& ec) + { + JSONCONS_TRY + { + if (options_.lossless_number()) + { + more_ = visitor.string_value(string_buffer_, semantic_tag::bigdec, *this, ec); + } + else + { + double d = to_double_(string_buffer_.c_str(), string_buffer_.length()); + more_ = visitor.double_value(d, semantic_tag::none, *this, ec); + } + } + JSONCONS_CATCH(...) + { + more_ = err_handler_(json_errc::invalid_number, *this); + if (!more_) + { + ec = json_errc::invalid_number; + return; + } + more_ = visitor.null_value(semantic_tag::none, *this, ec); // recovery + } + + after_value(ec); + } + + void end_string_value(const char_type* s, std::size_t length, basic_json_visitor& visitor, std::error_code& ec) + { + string_view_type sv(s, length); + auto result = unicode_traits::validate(s, length); + if (result.ec != unicode_traits::conv_errc()) + { + translate_conv_errc(result.ec,ec); + position_ += (result.ptr - s); + return; + } + switch (parent()) + { + case json_parse_state::member_name: + more_ = visitor.key(sv, *this, ec); + pop_state(); + state_ = json_parse_state::expect_colon; + break; + case json_parse_state::object: + case json_parse_state::array: + { + auto it = std::find_if(string_double_map_.begin(), string_double_map_.end(), string_maps_to_double{ sv }); + if (it != string_double_map_.end()) + { + more_ = visitor.double_value(it->second, semantic_tag::none, *this, ec); + } + else + { + more_ = visitor.string_value(sv, semantic_tag::none, *this, ec); + } + state_ = json_parse_state::expect_comma_or_end; + break; + } + case json_parse_state::root: + { + auto it = std::find_if(string_double_map_.begin(),string_double_map_.end(),string_maps_to_double{sv}); + if (it != string_double_map_.end()) + { + more_ = visitor.double_value(it->second, semantic_tag::none, *this, ec); + } + else + { + more_ = visitor.string_value(sv, semantic_tag::none, *this, ec); + } + state_ = json_parse_state::accept; + break; + } + default: + more_ = err_handler_(json_errc::syntax_error, *this); + if (!more_) + { + ec = json_errc::syntax_error; + return; + } + break; + } + } + + void begin_member_or_element(std::error_code& ec) + { + switch (parent()) + { + case json_parse_state::object: + state_ = json_parse_state::expect_member_name; + break; + case json_parse_state::array: + state_ = json_parse_state::expect_value; + break; + case json_parse_state::root: + break; + default: + more_ = err_handler_(json_errc::syntax_error, *this); + if (!more_) + { + ec = json_errc::syntax_error; + return; + } + break; + } + } + + void after_value(std::error_code& ec) + { + switch (parent()) + { + case json_parse_state::array: + case json_parse_state::object: + state_ = json_parse_state::expect_comma_or_end; + break; + case json_parse_state::root: + state_ = json_parse_state::accept; + break; + default: + more_ = err_handler_(json_errc::syntax_error, *this); + if (!more_) + { + ec = json_errc::syntax_error; + return; + } + break; + } + } + + void push_state(json_parse_state state) + { + state_stack_.push_back(state); + //std::cout << "max_nesting_depth: " << options_.max_nesting_depth() << ", capacity: " << state_stack_.capacity() << ", nesting_depth: " << nesting_depth_ << ", stack size: " << state_stack_.size() << "\n"; + } + + json_parse_state pop_state() + { + JSONCONS_ASSERT(!state_stack_.empty()) + json_parse_state state = state_stack_.back(); + state_stack_.pop_back(); + return state; + } + + uint32_t append_to_codepoint(uint32_t cp, int c, std::error_code& ec) + { + cp *= 16; + if (c >= '0' && c <= '9') + { + cp += c - '0'; + } + else if (c >= 'a' && c <= 'f') + { + cp += c - 'a' + 10; + } + else if (c >= 'A' && c <= 'F') + { + cp += c - 'A' + 10; + } + else + { + more_ = err_handler_(json_errc::invalid_unicode_escape_sequence, *this); + if (!more_) + { + ec = json_errc::invalid_unicode_escape_sequence; + return cp; + } + } + return cp; + } +}; + +using json_parser = basic_json_parser; +using wjson_parser = basic_json_parser; + +} + +#endif + diff --git a/third_party/jsoncons/json_reader.hpp b/third_party/jsoncons/json_reader.hpp new file mode 100644 index 0000000000..7b40abd536 --- /dev/null +++ b/third_party/jsoncons/json_reader.hpp @@ -0,0 +1,419 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_READER_HPP +#define JSONCONS_JSON_READER_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::move +#include +#include +#include +#include +#include + +namespace jsoncons { + + // utf8_other_json_input_adapter + + template + class json_utf8_to_other_visitor_adaptor : public json_visitor + { + public: + using json_visitor::string_view_type; + private: + basic_default_json_visitor default_visitor_; + basic_json_visitor& other_visitor_; + //std::function err_handler_; + + // noncopyable and nonmoveable + json_utf8_to_other_visitor_adaptor(const json_utf8_to_other_visitor_adaptor&) = delete; + json_utf8_to_other_visitor_adaptor& operator=(const json_utf8_to_other_visitor_adaptor&) = delete; + + public: + json_utf8_to_other_visitor_adaptor() + : other_visitor_(default_visitor_) + { + } + + json_utf8_to_other_visitor_adaptor(basic_json_visitor& other_visitor/*, + std::function err_handler*/) + : other_visitor_(other_visitor)/*, + err_handler_(err_handler)*/ + { + } + + private: + + void visit_flush() override + { + other_visitor_.flush(); + } + + bool visit_begin_object(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return other_visitor_.begin_object(tag, context, ec); + } + + bool visit_end_object(const ser_context& context, std::error_code& ec) override + { + return other_visitor_.end_object(context, ec); + } + + bool visit_begin_array(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return other_visitor_.begin_array(tag, context, ec); + } + + bool visit_end_array(const ser_context& context, std::error_code& ec) override + { + return other_visitor_.end_array(context, ec); + } + + bool visit_key(const string_view_type& name, const ser_context& context, std::error_code& ec) override + { + std::basic_string target; + auto result = unicode_traits::convert( + name.data(), name.size(), target, + unicode_traits::conv_flags::strict); + if (result.ec != unicode_traits::conv_errc()) + { + JSONCONS_THROW(ser_error(result.ec,context.line(),context.column())); + } + return other_visitor_.key(target, context, ec); + } + + bool visit_string(const string_view_type& value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + std::basic_string target; + auto result = unicode_traits::convert( + value.data(), value.size(), target, + unicode_traits::conv_flags::strict); + if (result.ec != unicode_traits::conv_errc()) + { + ec = result.ec; + return false; + } + return other_visitor_.string_value(target, tag, context, ec); + } + + bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return other_visitor_.int64_value(value, tag, context, ec); + } + + bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return other_visitor_.uint64_value(value, tag, context, ec); + } + + bool visit_half(uint16_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return other_visitor_.half_value(value, tag, context, ec); + } + + bool visit_double(double value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + return other_visitor_.double_value(value, tag, context, ec); + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return other_visitor_.bool_value(value, tag, context, ec); + } + + bool visit_null(semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + return other_visitor_.null_value(tag, context, ec); + } + }; + + template ,typename TempAllocator =std::allocator> + class basic_json_reader + { + public: + using char_type = CharT; + using source_type = Source; + using string_view_type = jsoncons::basic_string_view; + private: + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + static constexpr size_t default_max_buffer_size = 16384; + + json_source_adaptor source_; + basic_default_json_visitor default_visitor_; + basic_json_visitor& visitor_; + basic_json_parser parser_; + + // Noncopyable and nonmoveable + basic_json_reader(const basic_json_reader&) = delete; + basic_json_reader& operator=(const basic_json_reader&) = delete; + + public: + template + explicit basic_json_reader(Sourceable&& source, const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_reader(std::forward(source), + default_visitor_, + basic_json_decode_options(), + default_json_parsing(), + temp_alloc) + { + } + + template + basic_json_reader(Sourceable&& source, + const basic_json_decode_options& options, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_reader(std::forward(source), + default_visitor_, + options, + options.err_handler(), + temp_alloc) + { + } + + template + basic_json_reader(Sourceable&& source, + std::function err_handler, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_reader(std::forward(source), + default_visitor_, + basic_json_decode_options(), + err_handler, + temp_alloc) + { + } + + template + basic_json_reader(Sourceable&& source, + const basic_json_decode_options& options, + std::function err_handler, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_reader(std::forward(source), + default_visitor_, + options, + err_handler, + temp_alloc) + { + } + + template + basic_json_reader(Sourceable&& source, + basic_json_visitor& visitor, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_reader(std::forward(source), + visitor, + basic_json_decode_options(), + default_json_parsing(), + temp_alloc) + { + } + + template + basic_json_reader(Sourceable&& source, + basic_json_visitor& visitor, + const basic_json_decode_options& options, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_reader(std::forward(source), + visitor, + options, + options.err_handler(), + temp_alloc) + { + } + + template + basic_json_reader(Sourceable&& source, + basic_json_visitor& visitor, + std::function err_handler, + const TempAllocator& temp_alloc = TempAllocator()) + : basic_json_reader(std::forward(source), + visitor, + basic_json_decode_options(), + err_handler, + temp_alloc) + { + } + + template + basic_json_reader(Sourceable&& source, + basic_json_visitor& visitor, + const basic_json_decode_options& options, + std::function err_handler, + const TempAllocator& temp_alloc = TempAllocator()) + : source_(std::forward(source)), + visitor_(visitor), + parser_(options,err_handler,temp_alloc) + { + } + + void read_next() + { + std::error_code ec; + read_next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_next(std::error_code& ec) + { + if (source_.is_error()) + { + ec = json_errc::source_error; + return; + } + parser_.reset(); + while (!parser_.stopped()) + { + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + } + } + bool eof = parser_.source_exhausted(); + parser_.parse_some(visitor_, ec); + if (ec) return; + if (eof) + { + if (parser_.enter()) + { + break; + } + else if (!parser_.accept()) + { + ec = json_errc::unexpected_eof; + return; + } + } + } + + while (!source_.eof()) + { + parser_.skip_whitespace(); + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + } + } + else + { + break; + } + } + } + + void check_done() + { + std::error_code ec; + check_done(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + std::size_t line() const + { + return parser_.line(); + } + + std::size_t column() const + { + return parser_.column(); + } + + void check_done(std::error_code& ec) + { + if (source_.is_error()) + { + ec = json_errc::source_error; + return; + } + if (source_.eof()) + { + parser_.check_done(ec); + if (ec) return; + } + else + { + do + { + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + } + } + if (!parser_.source_exhausted()) + { + parser_.check_done(ec); + if (ec) return; + } + } + while (!eof()); + } + } + + bool eof() const + { + return parser_.source_exhausted() && source_.eof(); + } + + void read() + { + read_next(); + check_done(); + } + + void read(std::error_code& ec) + { + read_next(ec); + if (!ec) + { + check_done(ec); + } + } + }; + + using json_string_reader = basic_json_reader>; + using wjson_string_reader = basic_json_reader>; + using json_stream_reader = basic_json_reader>; + using wjson_stream_reader = basic_json_reader>; + +} + +#endif + diff --git a/third_party/jsoncons/json_traits_macros.hpp b/third_party/jsoncons/json_traits_macros.hpp new file mode 100644 index 0000000000..8727ffdf04 --- /dev/null +++ b/third_party/jsoncons/json_traits_macros.hpp @@ -0,0 +1,1072 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_TRAITS_MACROS_HPP +#define JSONCONS_JSON_TRAITS_MACROS_HPP + +#include // std::swap +#include // std::iterator_traits, std::input_iterator_tag +#include // JSONCONS_EXPAND, JSONCONS_QUOTE +#include +#include +#include // std::numeric_limits +#include +#include // std::enable_if +#include +#include + +namespace jsoncons +{ + #define JSONCONS_RDONLY(X) + + #define JSONCONS_RDWR(X) X + + struct always_true + { + template< typename T> + constexpr bool operator()(const T&) const noexcept + { + return true; + } + }; + + struct identity + { + template< typename T> + constexpr T&& operator()(T&& val) const noexcept + { + return std::forward(val); + } + }; + + template + struct json_traits_macro_names + {}; + + template + struct json_traits_helper + { + using string_view_type = typename Json::string_view_type; + + template + static void set_udt_member(const Json&, const string_view_type&, const OutputType&) + { + } + template + static void set_udt_member(const Json& j, const string_view_type& key, OutputType& val) + { + val = j.at(key).template as(); + } + + template + static void set_udt_member(const Json&, const string_view_type&, From, const OutputType&) + { + } + template + static void set_udt_member(const Json& j, const string_view_type& key, From from, OutputType& val) + { + val = from(j.at(key).template as()); + } + template + static void set_optional_json_member(const string_view_type& key, const std::shared_ptr& val, Json& j) + { + if (val) j.try_emplace(key, val); + } + template + static void set_optional_json_member(const string_view_type& key, const std::unique_ptr& val, Json& j) + { + if (val) j.try_emplace(key, val); + } + template + static void set_optional_json_member(const string_view_type& key, const jsoncons::optional& val, Json& j) + { + if (val) j.try_emplace(key, val); + } + template + static void set_optional_json_member(const string_view_type& key, const U& val, Json& j) + { + j.try_emplace(key, val); + } + }; +} + +#if defined(_MSC_VER) +#pragma warning( disable : 4127) +#endif + +#define JSONCONS_CONCAT_IMPL(a, b) a ## b +#define JSONCONS_CONCAT(a, b) JSONCONS_CONCAT_IMPL(a, b) + +// Inspired by https://github.com/Loki-Astari/ThorsSerializer/blob/master/src/Serialize/Traits.h + +#define JSONCONS_NARGS(...) JSONCONS_NARG_(__VA_ARGS__, 70,69,68,67,66,65,64,63,62,61,60,59,58,57,56,55,54,53,52,51,50,49,48,47,46,45,44,43,42,41,40,39,38,37,36,35,34,33,32,31,30,29,28,27,26,25,24,23,22,21,20,19,18,17,16,15,14,13,12,11,10,9,8,7,6,5,4,3,2,1,0) +#define JSONCONS_NARG_(...) JSONCONS_EXPAND( JSONCONS_ARG_N(__VA_ARGS__) ) +#define JSONCONS_ARG_N(e1,e2,e3,e4,e5,e6,e7,e8,e9,e10,e11,e12,e13,e14,e15,e16,e17,e18,e19,e20,e21,e22,e23,e24,e25,e26,e27,e28,e29,e30,e31,e32,e33,e34,e35,e36,e37,e38,e39,e40,e41,e42,e43,e44,e45,e46,e47,e48,e49,e50,e51,e52,e53,e54,e55,e56,e57,e58,e59,e60,e61,e62,e63,e64,e65,e66,e67,e68,e69,e70,N,...)N + +#define JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, Count) Call(P1, P2, P3, P4, Count) + +#define JSONKONS_VARIADIC_FOR_EACH(Call, P1, P2, P3, ...) JSONCONS_VARIADIC_REP_OF_N(Call, P1,P2, P3, JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) +#define JSONCONS_VARIADIC_REP_OF_N(Call, P1, P2, P3, Count, ...) JSONCONS_VARIADIC_REP_OF_N_(Call, P1, P2, P3, Count, __VA_ARGS__) +#define JSONCONS_VARIADIC_REP_OF_N_(Call, P1, P2, P3, Count, ...) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_ ## Count(Call, P1, P2, P3, __VA_ARGS__)) + +#define JSONCONS_VARIADIC_REP_OF_70(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 70) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_69(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_69(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 69) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_68(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_68(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 68) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_67(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_67(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 67) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_66(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_66(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 66) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_65(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_65(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 65) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_64(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_64(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 64) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_63(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_63(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 63) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_62(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_62(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 62) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_61(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_61(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 61) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_60(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_60(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 60) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_59(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_59(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 59) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_58(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_58(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 58) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_57(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_57(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 57) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_56(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_56(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 56) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_55(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_55(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 55) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_54(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_54(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 54) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_53(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_53(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 53) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_52(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_52(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 52) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_51(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_51(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 51) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_50(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_50(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 50) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_49(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_49(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 49) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_48(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_48(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 48) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_47(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_47(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 47) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_46(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_46(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 46) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_45(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_45(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 45) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_44(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_44(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 44) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_43(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_43(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 43) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_42(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_42(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 42) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_41(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_41(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 41) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_40(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_40(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 40) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_39(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_39(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 39) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_38(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_38(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 38) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_37(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_37(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 37) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_36(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_36(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 36) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_35(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_35(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 35) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_34(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_34(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 34) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_33(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_33(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 33) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_32(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_32(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 32) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_31(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_31(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 31) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_30(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_30(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 30) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_29(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_29(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 29) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_28(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_28(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 28) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_27(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_27(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 27) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_26(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_26(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 26) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_25(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_25(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 25) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_24(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_24(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 24) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_23(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_23(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 23) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_22(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_22(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 22) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_21(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_21(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 21) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_20(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_20(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 20) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_19(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_19(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 19) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_18(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_18(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 18) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_17(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_17(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 17) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_16(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_16(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 16) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_15(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_15(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 15) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_14(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_14(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 14) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_13(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_13(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 13) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_12(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_12(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 12) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_11(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_11(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 11) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_10(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_10(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 10) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_9(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_9(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 9) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_8(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_8(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 8) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_7(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_7(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 7) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_6(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_6(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 6) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_5(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_5(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 5) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_4(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_4(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 4) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_3(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_3(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 3) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_2(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_2(Call, P1, P2, P3, P4, ...) JSONCONS_EXPAND_CALL5(Call, P1, P2, P3, P4, 2) JSONCONS_EXPAND(JSONCONS_VARIADIC_REP_OF_1(Call, P1, P2, P3, __VA_ARGS__)) +#define JSONCONS_VARIADIC_REP_OF_1(Call, P1, P2, P3, P4) JSONCONS_EXPAND(Call ## _LAST(P1, P2, P3, P4, 1)) + +#define JSONCONS_TYPE_TRAITS_FRIEND \ + template \ + friend struct jsoncons::json_type_traits; + +#define JSONCONS_EXPAND_CALL2(Call, Expr, Id) JSONCONS_EXPAND(Call(Expr, Id)) + +#define JSONCONS_REP_OF_N(Call, Expr, Pre, App, Count) JSONCONS_REP_OF_ ## Count(Call, Expr, Pre, App) + +#define JSONCONS_REP_OF_50(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 50) JSONCONS_REP_OF_49(Call, Expr, , App) +#define JSONCONS_REP_OF_49(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 49) JSONCONS_REP_OF_48(Call, Expr, , App) +#define JSONCONS_REP_OF_48(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 48) JSONCONS_REP_OF_47(Call, Expr, , App) +#define JSONCONS_REP_OF_47(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 47) JSONCONS_REP_OF_46(Call, Expr, , App) +#define JSONCONS_REP_OF_46(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 46) JSONCONS_REP_OF_45(Call, Expr, , App) +#define JSONCONS_REP_OF_45(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 45) JSONCONS_REP_OF_44(Call, Expr, , App) +#define JSONCONS_REP_OF_44(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 44) JSONCONS_REP_OF_43(Call, Expr, , App) +#define JSONCONS_REP_OF_43(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 43) JSONCONS_REP_OF_42(Call, Expr, , App) +#define JSONCONS_REP_OF_42(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 42) JSONCONS_REP_OF_41(Call, Expr, , App) +#define JSONCONS_REP_OF_41(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 41) JSONCONS_REP_OF_40(Call, Expr, , App) +#define JSONCONS_REP_OF_40(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 40) JSONCONS_REP_OF_39(Call, Expr, , App) +#define JSONCONS_REP_OF_39(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 39) JSONCONS_REP_OF_38(Call, Expr, , App) +#define JSONCONS_REP_OF_38(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 38) JSONCONS_REP_OF_37(Call, Expr, , App) +#define JSONCONS_REP_OF_37(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 37) JSONCONS_REP_OF_36(Call, Expr, , App) +#define JSONCONS_REP_OF_36(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 36) JSONCONS_REP_OF_35(Call, Expr, , App) +#define JSONCONS_REP_OF_35(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 35) JSONCONS_REP_OF_34(Call, Expr, , App) +#define JSONCONS_REP_OF_34(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 34) JSONCONS_REP_OF_33(Call, Expr, , App) +#define JSONCONS_REP_OF_33(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 33) JSONCONS_REP_OF_32(Call, Expr, , App) +#define JSONCONS_REP_OF_32(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 32) JSONCONS_REP_OF_31(Call, Expr, , App) +#define JSONCONS_REP_OF_31(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 31) JSONCONS_REP_OF_30(Call, Expr, , App) +#define JSONCONS_REP_OF_30(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 30) JSONCONS_REP_OF_29(Call, Expr, , App) +#define JSONCONS_REP_OF_29(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 29) JSONCONS_REP_OF_28(Call, Expr, , App) +#define JSONCONS_REP_OF_28(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 28) JSONCONS_REP_OF_27(Call, Expr, , App) +#define JSONCONS_REP_OF_27(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 27) JSONCONS_REP_OF_26(Call, Expr, , App) +#define JSONCONS_REP_OF_26(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 26) JSONCONS_REP_OF_25(Call, Expr, , App) +#define JSONCONS_REP_OF_25(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 25) JSONCONS_REP_OF_24(Call, Expr, , App) +#define JSONCONS_REP_OF_24(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 24) JSONCONS_REP_OF_23(Call, Expr, , App) +#define JSONCONS_REP_OF_23(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 23) JSONCONS_REP_OF_22(Call, Expr, , App) +#define JSONCONS_REP_OF_22(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 22) JSONCONS_REP_OF_21(Call, Expr, , App) +#define JSONCONS_REP_OF_21(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 21) JSONCONS_REP_OF_20(Call, Expr, , App) +#define JSONCONS_REP_OF_20(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 20) JSONCONS_REP_OF_19(Call, Expr, , App) +#define JSONCONS_REP_OF_19(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 19) JSONCONS_REP_OF_18(Call, Expr, , App) +#define JSONCONS_REP_OF_18(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 18) JSONCONS_REP_OF_17(Call, Expr, , App) +#define JSONCONS_REP_OF_17(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 17) JSONCONS_REP_OF_16(Call, Expr, , App) +#define JSONCONS_REP_OF_16(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 16) JSONCONS_REP_OF_15(Call, Expr, , App) +#define JSONCONS_REP_OF_15(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 15) JSONCONS_REP_OF_14(Call, Expr, , App) +#define JSONCONS_REP_OF_14(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 14) JSONCONS_REP_OF_13(Call, Expr, , App) +#define JSONCONS_REP_OF_13(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 13) JSONCONS_REP_OF_12(Call, Expr, , App) +#define JSONCONS_REP_OF_12(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 12) JSONCONS_REP_OF_11(Call, Expr, , App) +#define JSONCONS_REP_OF_11(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 11) JSONCONS_REP_OF_10(Call, Expr, , App) +#define JSONCONS_REP_OF_10(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 10) JSONCONS_REP_OF_9(Call, Expr, , App) +#define JSONCONS_REP_OF_9(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 9) JSONCONS_REP_OF_8(Call, Expr, , App) +#define JSONCONS_REP_OF_8(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 8) JSONCONS_REP_OF_7(Call, Expr, , App) +#define JSONCONS_REP_OF_7(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 7) JSONCONS_REP_OF_6(Call, Expr, , App) +#define JSONCONS_REP_OF_6(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 6) JSONCONS_REP_OF_5(Call, Expr, , App) +#define JSONCONS_REP_OF_5(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 5) JSONCONS_REP_OF_4(Call, Expr, , App) +#define JSONCONS_REP_OF_4(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 4) JSONCONS_REP_OF_3(Call, Expr, , App) +#define JSONCONS_REP_OF_3(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 3) JSONCONS_REP_OF_2(Call, Expr, , App) +#define JSONCONS_REP_OF_2(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call, Expr, 2) JSONCONS_REP_OF_1(Call, Expr, , App) +#define JSONCONS_REP_OF_1(Call, Expr, Pre, App) Pre JSONCONS_EXPAND_CALL2(Call ## _LAST, Expr, 1) App +#define JSONCONS_REP_OF_0(Call, Expr, Pre, App) + +#define JSONCONS_GENERATE_TPL_PARAMS(Call, Count) JSONCONS_REP_OF_N(Call, , , ,Count) +#define JSONCONS_GENERATE_TPL_ARGS(Call, Count) JSONCONS_REP_OF_N(Call, ,<,>,Count) +#define JSONCONS_GENERATE_TPL_PARAM(Expr, Id) typename T ## Id, +#define JSONCONS_GENERATE_TPL_PARAM_LAST(Expr, Id) typename T ## Id +#define JSONCONS_GENERATE_MORE_TPL_PARAM(Expr, Id) ,typename T ## Id +#define JSONCONS_GENERATE_MORE_TPL_PARAM_LAST(Expr, Id) ,typename T ## Id +#define JSONCONS_GENERATE_TPL_ARG(Expr, Id) T ## Id, +#define JSONCONS_GENERATE_TPL_ARG_LAST(Ex, Id) T ## Id + +#define JSONCONS_GENERATE_NAME_STR(Prefix, P2, P3, Member, Count) JSONCONS_GENERATE_NAME_STR_LAST(Prefix, P2, P3, Member, Count) +#define JSONCONS_GENERATE_NAME_STR_LAST(Prefix, P2, P3, Member, Count) \ + static inline const char* Member ## _str(char) {return JSONCONS_QUOTE(,Member);} \ + static inline const wchar_t* Member ## _str(wchar_t) {return JSONCONS_QUOTE(L,Member);} \ + /**/ + +#define JSONCONS_N_MEMBER_IS(Prefix, P2, P3, Member, Count) JSONCONS_N_MEMBER_IS_LAST(Prefix, P2, P3, Member, Count) +#define JSONCONS_N_MEMBER_IS_LAST(Prefix, P2, P3, Member, Count) if ((num_params-Count) < num_mandatory_params1 && !ajson.contains(json_traits_macro_names::Member##_str(char_type{}))) return false; + +#define JSONCONS_N_MEMBER_AS(Prefix,P2,P3, Member, Count) JSONCONS_N_MEMBER_AS_LAST(Prefix,P2,P3, Member, Count) +#define JSONCONS_N_MEMBER_AS_LAST(Prefix,P2,P3, Member, Count) \ + if ((num_params-Count) < num_mandatory_params2 || ajson.contains(json_traits_macro_names::Member##_str(char_type{}))) \ + {json_traits_helper::set_udt_member(ajson,json_traits_macro_names::Member##_str(char_type{}),class_instance.Member);} + +#define JSONCONS_ALL_MEMBER_AS(Prefix, P2,P3,Member, Count) JSONCONS_ALL_MEMBER_AS_LAST(Prefix,P2,P3, Member, Count) +#define JSONCONS_ALL_MEMBER_AS_LAST(Prefix,P2,P3, Member, Count) \ + json_traits_helper::set_udt_member(ajson,json_traits_macro_names::Member##_str(char_type{}),class_instance.Member); + +#define JSONCONS_TO_JSON(Prefix, P2, P3, Member, Count) JSONCONS_TO_JSON_LAST(Prefix, P2, P3, Member, Count) +#define JSONCONS_TO_JSON_LAST(Prefix, P2, P3, Member, Count) if ((num_params-Count) < num_mandatory_params2) \ + {ajson.try_emplace(json_traits_macro_names::Member##_str(char_type{}),class_instance.Member);} \ + else {json_traits_helper::set_optional_json_member(json_traits_macro_names::Member##_str(char_type{}),class_instance.Member, ajson);} + +#define JSONCONS_ALL_TO_JSON(Prefix, P2, P3, Member, Count) JSONCONS_ALL_TO_JSON_LAST(Prefix, P2, P3, Member, Count) +#define JSONCONS_ALL_TO_JSON_LAST(Prefix, P2, P3, Member, Count) \ + ajson.try_emplace(json_traits_macro_names::Member##_str(char_type{}),class_instance.Member); + +#define JSONCONS_MEMBER_TRAITS_BASE(AsT,ToJ,NumTemplateParams,ClassType,NumMandatoryParams1,NumMandatoryParams2, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_traits_macro_names \ + { \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_GENERATE_NAME_STR, ,,, __VA_ARGS__)\ + }; \ + template \ + struct json_type_traits \ + { \ + using class_type = ClassType JSONCONS_GENERATE_TPL_ARGS(JSONCONS_GENERATE_TPL_ARG, NumTemplateParams); \ + using allocator_type = typename Json::allocator_type; \ + using char_type = typename Json::char_type; \ + using string_view_type = typename Json::string_view_type; \ + constexpr static size_t num_params = JSONCONS_NARGS(__VA_ARGS__); \ + constexpr static size_t num_mandatory_params1 = NumMandatoryParams1; \ + constexpr static size_t num_mandatory_params2 = NumMandatoryParams2; \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_N_MEMBER_IS, ,,, __VA_ARGS__)\ + return true; \ + } \ + static class_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # ClassType)); \ + class_type class_instance{}; \ + JSONKONS_VARIADIC_FOR_EACH(AsT, ,,, __VA_ARGS__) \ + return class_instance; \ + } \ + static Json to_json(const class_type& class_instance, allocator_type alloc=allocator_type()) \ + { \ + Json ajson(json_object_arg, semantic_tag::none, alloc); \ + JSONKONS_VARIADIC_FOR_EACH(ToJ, ,,, __VA_ARGS__) \ + return ajson; \ + } \ + }; \ +} \ + /**/ + +#define JSONCONS_N_MEMBER_TRAITS(ClassType,NumMandatoryParams,...) \ + JSONCONS_MEMBER_TRAITS_BASE(JSONCONS_N_MEMBER_AS, JSONCONS_TO_JSON,0, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_N_MEMBER_TRAITS(NumTemplateParams, ClassType,NumMandatoryParams, ...) \ + JSONCONS_MEMBER_TRAITS_BASE(JSONCONS_N_MEMBER_AS, JSONCONS_TO_JSON,NumTemplateParams, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_ALL_MEMBER_TRAITS(ClassType, ...) \ + JSONCONS_MEMBER_TRAITS_BASE(JSONCONS_ALL_MEMBER_AS,JSONCONS_ALL_TO_JSON,0,ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__),__VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_ALL_MEMBER_TRAITS(NumTemplateParams, ClassType, ...) \ + JSONCONS_MEMBER_TRAITS_BASE(JSONCONS_ALL_MEMBER_AS,JSONCONS_ALL_TO_JSON,NumTemplateParams,ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__),__VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_MEMBER_NAME_IS(P1, P2, P3, Seq, Count) JSONCONS_MEMBER_NAME_IS_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_MEMBER_NAME_IS_LAST(P1, P2, P3, Seq, Count) if ((num_params-Count) < num_mandatory_params1 && JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_MEMBER_NAME_IS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_MEMBER_NAME_IS_2(Member, Name) !ajson.contains(Name)) return false; +#define JSONCONS_MEMBER_NAME_IS_3(Member, Name, Mode) JSONCONS_MEMBER_NAME_IS_2(Member, Name) +#define JSONCONS_MEMBER_NAME_IS_4(Member, Name, Mode, Match) JSONCONS_MEMBER_NAME_IS_6(Member, Name, Mode, Match, , ) +#define JSONCONS_MEMBER_NAME_IS_5(Member, Name, Mode, Match, Into) JSONCONS_MEMBER_NAME_IS_6(Member, Name, Mode, Match, Into, ) +#define JSONCONS_MEMBER_NAME_IS_6(Member, Name, Mode, Match, Into, From) !ajson.contains(Name)) return false; \ + JSONCONS_TRY{if (!Match(ajson.at(Name).template as())->Member))>::type>())) return false;} \ + JSONCONS_CATCH(...) {return false;} + +#define JSONCONS_N_MEMBER_NAME_AS(P1, P2, P3, Seq, Count) JSONCONS_N_MEMBER_NAME_AS_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_N_MEMBER_NAME_AS_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_N_MEMBER_NAME_AS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_N_MEMBER_NAME_AS_2(Member, Name) \ + if (ajson.contains(Name)) {json_traits_helper::set_udt_member(ajson,Name,class_instance.Member);} +#define JSONCONS_N_MEMBER_NAME_AS_3(Member, Name, Mode) Mode(JSONCONS_N_MEMBER_NAME_AS_2(Member, Name)) +#define JSONCONS_N_MEMBER_NAME_AS_4(Member, Name, Mode, Match) \ + Mode(if (ajson.contains(Name)) {json_traits_helper::set_udt_member(ajson,Name,class_instance.Member);}) +#define JSONCONS_N_MEMBER_NAME_AS_5(Member, Name, Mode, Match, Into) \ + Mode(if (ajson.contains(Name)) {json_traits_helper::template set_udt_member())->Member))>::type>(ajson,Name,class_instance.Member);}) +#define JSONCONS_N_MEMBER_NAME_AS_6(Member, Name, Mode, Match, Into, From) \ + Mode(if (ajson.contains(Name)) {json_traits_helper::template set_udt_member())->Member))>::type>(ajson,Name,From,class_instance.Member);}) + +#define JSONCONS_ALL_MEMBER_NAME_AS(P1, P2, P3, Seq, Count) JSONCONS_ALL_MEMBER_NAME_AS_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_ALL_MEMBER_NAME_AS_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_ALL_MEMBER_NAME_AS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_ALL_MEMBER_NAME_AS_2(Member, Name) \ + json_traits_helper::set_udt_member(ajson,Name,class_instance.Member); +#define JSONCONS_ALL_MEMBER_NAME_AS_3(Member, Name, Mode) Mode(JSONCONS_ALL_MEMBER_NAME_AS_2(Member, Name)) +#define JSONCONS_ALL_MEMBER_NAME_AS_4(Member, Name, Mode, Match) \ + Mode(json_traits_helper::set_udt_member(ajson,Name,class_instance.Member);) +#define JSONCONS_ALL_MEMBER_NAME_AS_5(Member, Name, Mode, Match, Into) \ + Mode(json_traits_helper::template set_udt_member())->Member))>::type>(ajson,Name,class_instance.Member);) +#define JSONCONS_ALL_MEMBER_NAME_AS_6(Member, Name, Mode, Match, Into, From) \ + Mode(json_traits_helper::template set_udt_member())->Member))>::type>(ajson,Name,From,class_instance.Member);) + +#define JSONCONS_N_MEMBER_NAME_TO_JSON(P1, P2, P3, Seq, Count) JSONCONS_N_MEMBER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_N_MEMBER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) if ((num_params-Count) < num_mandatory_params2) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_N_MEMBER_NAME_TO_JSON_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_N_MEMBER_NAME_TO_JSON_2(Member, Name) \ + {ajson.try_emplace(Name,class_instance.Member);} \ +else \ + {json_traits_helper::set_optional_json_member(Name,class_instance.Member, ajson);} +#define JSONCONS_N_MEMBER_NAME_TO_JSON_3(Member, Name, Mode) JSONCONS_N_MEMBER_NAME_TO_JSON_2(Member, Name) +#define JSONCONS_N_MEMBER_NAME_TO_JSON_4(Member, Name, Mode, Match) JSONCONS_N_MEMBER_NAME_TO_JSON_6(Member, Name, Mode, Match,,) +#define JSONCONS_N_MEMBER_NAME_TO_JSON_5(Member, Name, Mode, Match, Into) JSONCONS_N_MEMBER_NAME_TO_JSON_6(Member, Name, Mode, Match, Into, ) +#define JSONCONS_N_MEMBER_NAME_TO_JSON_6(Member, Name, Mode, Match, Into, From) \ + {ajson.try_emplace(Name, Into(class_instance.Member));} \ +else \ + {json_traits_helper::set_optional_json_member(Name, Into(class_instance.Member), ajson);} + +#define JSONCONS_ALL_MEMBER_NAME_TO_JSON(P1, P2, P3, Seq, Count) JSONCONS_ALL_MEMBER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_ALL_MEMBER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_ALL_MEMBER_NAME_TO_JSON_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_ALL_MEMBER_NAME_TO_JSON_2(Member, Name) ajson.try_emplace(Name,class_instance.Member); +#define JSONCONS_ALL_MEMBER_NAME_TO_JSON_3(Member, Name, Mode) JSONCONS_ALL_MEMBER_NAME_TO_JSON_2(Member, Name) +#define JSONCONS_ALL_MEMBER_NAME_TO_JSON_4(Member, Name, Mode, Match) JSONCONS_ALL_MEMBER_NAME_TO_JSON_6(Member, Name, Mode, Match,,) +#define JSONCONS_ALL_MEMBER_NAME_TO_JSON_5(Member, Name, Mode, Match, Into) JSONCONS_ALL_MEMBER_NAME_TO_JSON_6(Member, Name, Mode, Match, Into, ) +#define JSONCONS_ALL_MEMBER_NAME_TO_JSON_6(Member, Name, Mode, Match, Into, From) ajson.try_emplace(Name, Into(class_instance.Member)); + +#define JSONCONS_MEMBER_NAME_TRAITS_BASE(AsT,ToJ, NumTemplateParams, ClassType,NumMandatoryParams1,NumMandatoryParams2, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_type_traits \ + { \ + using class_type = ClassType JSONCONS_GENERATE_TPL_ARGS(JSONCONS_GENERATE_TPL_ARG, NumTemplateParams); \ + using allocator_type = typename Json::allocator_type; \ + using char_type = typename Json::char_type; \ + using string_view_type = typename Json::string_view_type; \ + constexpr static size_t num_params = JSONCONS_NARGS(__VA_ARGS__); \ + constexpr static size_t num_mandatory_params1 = NumMandatoryParams1; \ + constexpr static size_t num_mandatory_params2 = NumMandatoryParams2; \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_MEMBER_NAME_IS,,,, __VA_ARGS__)\ + return true; \ + } \ + static class_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # ClassType)); \ + class_type class_instance{}; \ + JSONKONS_VARIADIC_FOR_EACH(AsT,,,, __VA_ARGS__) \ + return class_instance; \ + } \ + static Json to_json(const class_type& class_instance, allocator_type alloc=allocator_type()) \ + { \ + Json ajson(json_object_arg, semantic_tag::none, alloc); \ + JSONKONS_VARIADIC_FOR_EACH(ToJ,,,, __VA_ARGS__) \ + return ajson; \ + } \ + }; \ +} \ + /**/ + + +#define JSONCONS_N_MEMBER_NAME_TRAITS(ClassType,NumMandatoryParams, ...) \ + JSONCONS_MEMBER_NAME_TRAITS_BASE(JSONCONS_N_MEMBER_NAME_AS, JSONCONS_N_MEMBER_NAME_TO_JSON, 0, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_N_MEMBER_NAME_TRAITS(NumTemplateParams, ClassType,NumMandatoryParams, ...) \ + JSONCONS_MEMBER_NAME_TRAITS_BASE(JSONCONS_N_MEMBER_NAME_AS, JSONCONS_N_MEMBER_NAME_TO_JSON, NumTemplateParams, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_ALL_MEMBER_NAME_TRAITS(ClassType, ...) \ + JSONCONS_MEMBER_NAME_TRAITS_BASE(JSONCONS_ALL_MEMBER_NAME_AS, JSONCONS_ALL_MEMBER_NAME_TO_JSON, 0, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_ALL_MEMBER_NAME_TRAITS(NumTemplateParams, ClassType, ...) \ + JSONCONS_MEMBER_NAME_TRAITS_BASE(JSONCONS_ALL_MEMBER_NAME_AS, JSONCONS_ALL_MEMBER_NAME_TO_JSON, NumTemplateParams, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_CTOR_GETTER_IS(Prefix, P2, P3, Getter, Count) JSONCONS_CTOR_GETTER_IS_LAST(Prefix, P2, P3, Getter, Count) +#define JSONCONS_CTOR_GETTER_IS_LAST(Prefix, P2, P3, Getter, Count) if ((num_params-Count) < num_mandatory_params1 && !ajson.contains(json_traits_macro_names::Getter##_str(char_type{}))) return false; + +#define JSONCONS_CTOR_GETTER_AS(Prefix, P2, P3, Getter, Count) JSONCONS_CTOR_GETTER_AS_LAST(Prefix, P2, P3, Getter, Count), +#define JSONCONS_CTOR_GETTER_AS_LAST(Prefix, P2, P3, Getter, Count) ((num_params-Count) < num_mandatory_params2) ? (ajson.at(json_traits_macro_names::Getter##_str(char_type{}))).template as())->Getter())>::type>() : (ajson.contains(json_traits_macro_names::Getter##_str(char_type{})) ? (ajson.at(json_traits_macro_names::Getter##_str(char_type{}))).template as())->Getter())>::type>() : typename std::decay())->Getter())>::type()) + +#define JSONCONS_CTOR_GETTER_TO_JSON(Prefix, P2, P3, Getter, Count) JSONCONS_CTOR_GETTER_TO_JSON_LAST(Prefix, P2, P3, Getter, Count) + +#define JSONCONS_CTOR_GETTER_TO_JSON_LAST(Prefix, P2, P3, Getter, Count) \ +if ((num_params-Count) < num_mandatory_params2) { \ + ajson.try_emplace(json_traits_macro_names::Getter##_str(char_type{}),class_instance.Getter() ); \ + } \ +else { \ + json_traits_helper::set_optional_json_member(json_traits_macro_names::Getter##_str(char_type{}),class_instance.Getter(), ajson); \ +} + +#define JSONCONS_CTOR_GETTER_TRAITS_BASE(NumTemplateParams, ClassType,NumMandatoryParams1,NumMandatoryParams2, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_traits_macro_names \ + { \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_GENERATE_NAME_STR, ,,, __VA_ARGS__)\ + }; \ + template \ + struct json_type_traits \ + { \ + using class_type = ClassType JSONCONS_GENERATE_TPL_ARGS(JSONCONS_GENERATE_TPL_ARG, NumTemplateParams); \ + using allocator_type = typename Json::allocator_type; \ + using char_type = typename Json::char_type; \ + using string_view_type = typename Json::string_view_type; \ + constexpr static size_t num_params = JSONCONS_NARGS(__VA_ARGS__); \ + constexpr static size_t num_mandatory_params1 = NumMandatoryParams1; \ + constexpr static size_t num_mandatory_params2 = NumMandatoryParams2; \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_CTOR_GETTER_IS, ,,, __VA_ARGS__)\ + return true; \ + } \ + static class_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # ClassType)); \ + return class_type ( JSONKONS_VARIADIC_FOR_EACH(JSONCONS_CTOR_GETTER_AS, ,,, __VA_ARGS__) ); \ + } \ + static Json to_json(const class_type& class_instance, allocator_type alloc=allocator_type()) \ + { \ + Json ajson(json_object_arg, semantic_tag::none, alloc); \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_CTOR_GETTER_TO_JSON, ,,, __VA_ARGS__) \ + return ajson; \ + } \ + }; \ +} \ + /**/ + +#define JSONCONS_ALL_CTOR_GETTER_TRAITS(ClassType, ...) \ + JSONCONS_CTOR_GETTER_TRAITS_BASE(0, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_ALL_CTOR_GETTER_TRAITS(NumTemplateParams, ClassType, ...) \ + JSONCONS_CTOR_GETTER_TRAITS_BASE(NumTemplateParams, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_N_CTOR_GETTER_TRAITS(ClassType,NumMandatoryParams, ...) \ + JSONCONS_CTOR_GETTER_TRAITS_BASE(0, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_N_ALL_CTOR_GETTER_TRAITS(NumTemplateParams, ClassType,NumMandatoryParams, ...) \ + JSONCONS_CTOR_GETTER_TRAITS_BASE(NumTemplateParams, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_CTOR_GETTER_NAME_IS(P1, P2, P3, Seq, Count) JSONCONS_CTOR_GETTER_NAME_IS_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_CTOR_GETTER_NAME_IS_LAST(P1, P2, P3, Seq, Count) if ((num_params-Count) < num_mandatory_params1 && JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_CTOR_GETTER_NAME_IS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_CTOR_GETTER_NAME_IS_2(Getter, Name) !ajson.contains(Name)) return false; +#define JSONCONS_CTOR_GETTER_NAME_IS_3(Getter, Name, Mode) JSONCONS_CTOR_GETTER_NAME_IS_2(Getter, Name) +#define JSONCONS_CTOR_GETTER_NAME_IS_4(Getter, Name, Mode, Match) JSONCONS_CTOR_GETTER_NAME_IS_6(Getter, Name, Mode, Match, , ) +#define JSONCONS_CTOR_GETTER_NAME_IS_5(Getter, Name, Mode, Match, Into) JSONCONS_CTOR_GETTER_NAME_IS_6(Getter, Name, Mode, Match, Into, ) +#define JSONCONS_CTOR_GETTER_NAME_IS_6(Getter, Name, Mode, Match, Into, From) !ajson.contains(Name)) return false; \ + JSONCONS_TRY{if (!Match(ajson.at(Name).template as())->Getter()))>::type>())) return false;} \ + JSONCONS_CATCH(...) {return false;} + +#define JSONCONS_CTOR_GETTER_NAME_AS(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_CTOR_GETTER_NAME_AS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_CTOR_GETTER_NAME_AS_2(Getter, Name) JSONCONS_CTOR_GETTER_NAME_AS_LAST_2(Getter, Name) JSONCONS_COMMA +#define JSONCONS_CTOR_GETTER_NAME_AS_3(Getter, Name, Mode) Mode(JSONCONS_CTOR_GETTER_NAME_AS_LAST_2(Getter, Name)) Mode(JSONCONS_COMMA) +#define JSONCONS_CTOR_GETTER_NAME_AS_4(Getter, Name, Mode, Match) JSONCONS_CTOR_GETTER_NAME_AS_6(Getter, Name, Mode, Match,,) +#define JSONCONS_CTOR_GETTER_NAME_AS_5(Getter, Name, Mode, Match, Into) JSONCONS_CTOR_GETTER_NAME_AS_6(Getter, Name, Mode, Match, Into, ) +#define JSONCONS_CTOR_GETTER_NAME_AS_6(Getter, Name, Mode, Match, Into, From) JSONCONS_CTOR_GETTER_NAME_AS_LAST_6(Getter,Name,Mode,Match,Into,From) Mode(JSONCONS_COMMA) +#define JSONCONS_COMMA , + +#define JSONCONS_CTOR_GETTER_NAME_AS_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_CTOR_GETTER_NAME_AS_LAST_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_CTOR_GETTER_NAME_AS_LAST_2(Getter, Name) (ajson.contains(Name)) ? (ajson.at(Name)).template as())->Getter())>::type>() : typename std::decay())->Getter())>::type() +#define JSONCONS_CTOR_GETTER_NAME_AS_LAST_3(Getter, Name, Mode) Mode(JSONCONS_CTOR_GETTER_NAME_AS_LAST_2(Getter, Name)) +#define JSONCONS_CTOR_GETTER_NAME_AS_LAST_4(Getter, Name, Mode, Match) JSONCONS_CTOR_GETTER_NAME_AS_LAST_6(Getter, Name, Mode, Match,,) +#define JSONCONS_CTOR_GETTER_NAME_AS_LAST_5(Getter, Name, Mode, Match, Into) JSONCONS_CTOR_GETTER_NAME_AS_LAST_6(Getter, Name, Mode, Match, Into, ) +#define JSONCONS_CTOR_GETTER_NAME_AS_LAST_6(Getter, Name, Mode, Match, Into, From) Mode(ajson.contains(Name) ? From(ajson.at(Name).template as())->Getter()))>::type>()) : From(typename std::decay())->Getter()))>::type())) + +#define JSONCONS_CTOR_GETTER_NAME_TO_JSON(P1, P2, P3, Seq, Count) JSONCONS_CTOR_GETTER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_CTOR_GETTER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) if ((num_params-Count) < num_mandatory_params2) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_CTOR_GETTER_NAME_TO_JSON_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_CTOR_GETTER_NAME_TO_JSON_2(Getter, Name) \ +{ \ + ajson.try_emplace(Name,class_instance.Getter() ); \ +} \ +else { \ + json_traits_helper::set_optional_json_member(Name,class_instance.Getter(), ajson); \ +} +#define JSONCONS_CTOR_GETTER_NAME_TO_JSON_3(Getter, Name, Mode) JSONCONS_CTOR_GETTER_NAME_TO_JSON_2(Getter, Name) +#define JSONCONS_CTOR_GETTER_NAME_TO_JSON_4(Getter, Name, Mode, Match) JSONCONS_CTOR_GETTER_NAME_TO_JSON_2(Getter, Name) +#define JSONCONS_CTOR_GETTER_NAME_TO_JSON_5(Getter, Name, Mode, Match, Into) JSONCONS_CTOR_GETTER_NAME_TO_JSON_6(Getter, Name, Mode, Match, Into, ) +#define JSONCONS_CTOR_GETTER_NAME_TO_JSON_6(Getter, Name, Mode, Match, Into, From) \ +{ \ + ajson.try_emplace(Name, Into(class_instance.Getter()) ); \ +} \ +else { \ + json_traits_helper::set_optional_json_member(Name, Into(class_instance.Getter()), ajson); \ +} + +#define JSONCONS_CTOR_GETTER_NAME_TRAITS_BASE(NumTemplateParams, ClassType,NumMandatoryParams1,NumMandatoryParams2, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_type_traits \ + { \ + using class_type = ClassType JSONCONS_GENERATE_TPL_ARGS(JSONCONS_GENERATE_TPL_ARG, NumTemplateParams); \ + using allocator_type = typename Json::allocator_type; \ + using char_type = typename Json::char_type; \ + using string_view_type = typename Json::string_view_type; \ + constexpr static size_t num_params = JSONCONS_NARGS(__VA_ARGS__); \ + constexpr static size_t num_mandatory_params1 = NumMandatoryParams1; \ + constexpr static size_t num_mandatory_params2 = NumMandatoryParams2; \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_CTOR_GETTER_NAME_IS,,,, __VA_ARGS__)\ + return true; \ + } \ + static class_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # ClassType)); \ + return class_type ( JSONKONS_VARIADIC_FOR_EACH(JSONCONS_CTOR_GETTER_NAME_AS,,,, __VA_ARGS__) ); \ + } \ + static Json to_json(const class_type& class_instance, allocator_type alloc=allocator_type()) \ + { \ + Json ajson(json_object_arg, semantic_tag::none, alloc); \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_CTOR_GETTER_NAME_TO_JSON,,,, __VA_ARGS__) \ + return ajson; \ + } \ + }; \ +} \ + /**/ + +#define JSONCONS_ALL_CTOR_GETTER_NAME_TRAITS(ClassType, ...) \ + JSONCONS_CTOR_GETTER_NAME_TRAITS_BASE(0, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_ALL_CTOR_GETTER_NAME_TRAITS(NumTemplateParams, ClassType, ...) \ + JSONCONS_CTOR_GETTER_NAME_TRAITS_BASE(NumTemplateParams, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_N_CTOR_GETTER_NAME_TRAITS(ClassType,NumMandatoryParams, ...) \ + JSONCONS_CTOR_GETTER_NAME_TRAITS_BASE(0, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_N_CTOR_GETTER_NAME_TRAITS(NumTemplateParams, ClassType,NumMandatoryParams, ...) \ +JSONCONS_CTOR_GETTER_NAME_TRAITS_BASE(NumTemplateParams, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_ENUM_PAIR(Prefix, P2, P3, Member, Count) JSONCONS_ENUM_PAIR_LAST(Prefix, P2, P3, Member, Count), +#define JSONCONS_ENUM_PAIR_LAST(Prefix, P2, P3, Member, Count) {enum_type::Member, json_traits_macro_names::Member##_str(char_type{})} + +#define JSONCONS_ENUM_TRAITS_BASE(EnumType, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_traits_macro_names \ + { \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_GENERATE_NAME_STR, ,,, __VA_ARGS__)\ + }; \ + template \ + struct json_type_traits \ + { \ + static_assert(std::is_enum::value, # EnumType " must be an enum"); \ + using enum_type = EnumType; \ + using char_type = typename Json::char_type; \ + using string_type = std::basic_string; \ + using string_view_type = jsoncons::basic_string_view; \ + using allocator_type = typename Json::allocator_type; \ + using mapped_type = std::pair; \ + \ + static std::pair get_values() \ + { \ + static const mapped_type v[] = { \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_ENUM_PAIR, ,,, __VA_ARGS__)\ + };\ + return std::make_pair(v,v+JSONCONS_NARGS(__VA_ARGS__)); \ + } \ + \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_string()) return false; \ + auto first = get_values().first; \ + auto last = get_values().second; \ + const string_view_type s = ajson.template as(); \ + if (s.empty() && std::find_if(first, last, \ + [](const mapped_type& item) -> bool \ + { return item.first == enum_type(); }) == last) \ + { \ + return true; \ + } \ + auto it = std::find_if(first, last, \ + [&](const mapped_type& item) -> bool \ + { return item.second == s; }); \ + return it != last; \ + } \ + static enum_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # EnumType)); \ + const string_view_type s = ajson.template as(); \ + auto first = get_values().first; \ + auto last = get_values().second; \ + if (s.empty() && std::find_if(first, last, \ + [](const mapped_type& item) -> bool \ + { return item.first == enum_type(); }) == last) \ + { \ + return enum_type(); \ + } \ + auto it = std::find_if(first, last, \ + [&](const mapped_type& item) -> bool \ + { return item.second == s; }); \ + if (it == last) \ + { \ + if (s.empty()) \ + { \ + return enum_type(); \ + } \ + else \ + { \ + JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not an enum")); \ + } \ + } \ + return it->first; \ + } \ + static Json to_json(enum_type class_instance, allocator_type alloc=allocator_type()) \ + { \ + static constexpr char_type empty_string[] = {0}; \ + auto first = get_values().first; \ + auto last = get_values().second; \ + auto it = std::find_if(first, last, \ + [class_instance](const mapped_type& item) -> bool \ + { return item.first == class_instance; }); \ + if (it == last) \ + { \ + if (class_instance == enum_type()) \ + { \ + return Json(empty_string); \ + } \ + else \ + { \ + JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not an enum")); \ + } \ + } \ + return Json(it->second,alloc); \ + } \ + }; \ +} \ + /**/ + +#define JSONCONS_ENUM_TRAITS(EnumType, ...) \ + JSONCONS_ENUM_TRAITS_BASE(EnumType,__VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_NAME_ENUM_PAIR(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_NAME_ENUM_PAIR_ Seq), +#define JSONCONS_NAME_ENUM_PAIR_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_NAME_ENUM_PAIR_ Seq) +#define JSONCONS_NAME_ENUM_PAIR_(Member, Name) {enum_type::Member, Name} + +#define JSONCONS_ENUM_NAME_TRAITS(EnumType, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_type_traits \ + { \ + static_assert(std::is_enum::value, # EnumType " must be an enum"); \ + using enum_type = EnumType; \ + using char_type = typename Json::char_type; \ + using string_type = std::basic_string; \ + using string_view_type = jsoncons::basic_string_view; \ + using allocator_type = typename Json::allocator_type; \ + using mapped_type = std::pair; \ + \ + static std::pair get_values() \ + { \ + static const mapped_type v[] = { \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_NAME_ENUM_PAIR,,,, __VA_ARGS__)\ + };\ + return std::make_pair(v,v+JSONCONS_NARGS(__VA_ARGS__)); \ + } \ + \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_string()) return false; \ + auto first = get_values().first; \ + auto last = get_values().second; \ + const string_view_type s = ajson.template as(); \ + if (s.empty() && std::find_if(first, last, \ + [](const mapped_type& item) -> bool \ + { return item.first == enum_type(); }) == last) \ + { \ + return true; \ + } \ + auto it = std::find_if(first, last, \ + [&](const mapped_type& item) -> bool \ + { return item.second == s; }); \ + return it != last; \ + } \ + static enum_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # EnumType)); \ + const string_view_type s = ajson.template as(); \ + auto first = get_values().first; \ + auto last = get_values().second; \ + if (s.empty() && std::find_if(first, last, \ + [](const mapped_type& item) -> bool \ + { return item.first == enum_type(); }) == last) \ + { \ + return enum_type(); \ + } \ + auto it = std::find_if(first, last, \ + [&](const mapped_type& item) -> bool \ + { return item.second == s; }); \ + if (it == last) \ + { \ + if (s.empty()) \ + { \ + return enum_type(); \ + } \ + else \ + { \ + JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not an enum")); \ + } \ + } \ + return it->first; \ + } \ + static Json to_json(enum_type class_instance, allocator_type alloc=allocator_type()) \ + { \ + static constexpr char_type empty_string[] = {0}; \ + auto first = get_values().first; \ + auto last = get_values().second; \ + auto it = std::find_if(first, last, \ + [class_instance](const mapped_type& item) -> bool \ + { return item.first == class_instance; }); \ + if (it == last) \ + { \ + if (class_instance == enum_type()) \ + { \ + return Json(empty_string); \ + } \ + else \ + { \ + JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not an enum")); \ + } \ + } \ + return Json(it->second,alloc); \ + } \ + }; \ + template <> struct is_json_type_traits_declared : public std::true_type {}; \ +} \ + /**/ + +#define JSONCONS_GETTER_SETTER_AS(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_GETTER_SETTER_AS_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_GETTER_SETTER_AS_LAST(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_GETTER_SETTER_AS_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_GETTER_SETTER_AS_(Prefix, Getter, Setter, Property, Count) if ((num_params-Count) < num_mandatory_params2 || ajson.contains(json_traits_macro_names::Property##_str(char_type{}))) {class_instance.Setter(ajson.at(json_traits_macro_names::Property##_str(char_type{})).template as::type>());} + +#define JSONCONS_ALL_GETTER_SETTER_AS(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_ALL_GETTER_SETTER_AS_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_ALL_GETTER_SETTER_AS_LAST(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_ALL_GETTER_SETTER_AS_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_ALL_GETTER_SETTER_AS_(Prefix, Getter, Setter, Property, Count) class_instance.Setter(ajson.at(json_traits_macro_names::Property##_str(char_type{})).template as::type>()); + +#define JSONCONS_GETTER_SETTER_TO_JSON(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_GETTER_SETTER_TO_JSON_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_GETTER_SETTER_TO_JSON_LAST(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_GETTER_SETTER_TO_JSON_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_GETTER_SETTER_TO_JSON_(Prefix, Getter, Setter, Property, Count) \ +if ((num_params-Count) < num_mandatory_params2) \ + {ajson.try_emplace(json_traits_macro_names::Property##_str(char_type{}),class_instance.Getter());} \ +else \ + {json_traits_helper::set_optional_json_member(json_traits_macro_names::Property##_str(char_type{}),class_instance.Getter(), ajson);} + +#define JSONCONS_ALL_GETTER_SETTER_TO_JSON(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_ALL_GETTER_SETTER_TO_JSON_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_ALL_GETTER_SETTER_TO_JSON_LAST(Prefix, GetPrefix, SetPrefix, Property, Count) JSONCONS_ALL_GETTER_SETTER_TO_JSON_(Prefix, GetPrefix ## Property, SetPrefix ## Property, Property, Count) +#define JSONCONS_ALL_GETTER_SETTER_TO_JSON_(Prefix, Getter, Setter, Property, Count) ajson.try_emplace(json_traits_macro_names::Property##_str(char_type{}),class_instance.Getter() ); + +#define JSONCONS_GETTER_SETTER_TRAITS_BASE(AsT,ToJ,NumTemplateParams, ClassType,GetPrefix,SetPrefix,NumMandatoryParams1,NumMandatoryParams2, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_traits_macro_names \ + { \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_GENERATE_NAME_STR, ,,, __VA_ARGS__)\ + }; \ + template \ + struct json_type_traits \ + { \ + using class_type = ClassType JSONCONS_GENERATE_TPL_ARGS(JSONCONS_GENERATE_TPL_ARG, NumTemplateParams); \ + using allocator_type = typename Json::allocator_type; \ + using char_type = typename Json::char_type; \ + using string_view_type = typename Json::string_view_type; \ + constexpr static size_t num_params = JSONCONS_NARGS(__VA_ARGS__); \ + constexpr static size_t num_mandatory_params1 = NumMandatoryParams1; \ + constexpr static size_t num_mandatory_params2 = NumMandatoryParams2; \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_N_MEMBER_IS, ,GetPrefix,SetPrefix, __VA_ARGS__)\ + return true; \ + } \ + static class_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # ClassType)); \ + class_type class_instance{}; \ + JSONKONS_VARIADIC_FOR_EACH(AsT, ,GetPrefix,SetPrefix, __VA_ARGS__) \ + return class_instance; \ + } \ + static Json to_json(const class_type& class_instance, allocator_type alloc=allocator_type()) \ + { \ + Json ajson(json_object_arg, semantic_tag::none, alloc); \ + JSONKONS_VARIADIC_FOR_EACH(ToJ, ,GetPrefix,SetPrefix, __VA_ARGS__) \ + return ajson; \ + } \ + }; \ +} \ + /**/ + +#define JSONCONS_N_GETTER_SETTER_TRAITS(ClassType,GetPrefix,SetPrefix,NumMandatoryParams, ...) \ + JSONCONS_GETTER_SETTER_TRAITS_BASE(JSONCONS_GETTER_SETTER_AS, JSONCONS_GETTER_SETTER_TO_JSON,0, ClassType,GetPrefix,SetPrefix,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_N_GETTER_SETTER_TRAITS(NumTemplateParams, ClassType,GetPrefix,SetPrefix,NumMandatoryParams, ...) \ + JSONCONS_GETTER_SETTER_TRAITS_BASE(JSONCONS_GETTER_SETTER_AS, JSONCONS_GETTER_SETTER_TO_JSON,NumTemplateParams, ClassType,GetPrefix,SetPrefix,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_ALL_GETTER_SETTER_TRAITS(ClassType,GetPrefix,SetPrefix, ...) \ + JSONCONS_GETTER_SETTER_TRAITS_BASE(JSONCONS_ALL_GETTER_SETTER_AS, JSONCONS_ALL_GETTER_SETTER_TO_JSON,0,ClassType,GetPrefix,SetPrefix, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__),__VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_ALL_GETTER_SETTER_TRAITS(NumTemplateParams, ClassType,GetPrefix,SetPrefix, ...) \ + JSONCONS_GETTER_SETTER_TRAITS_BASE(JSONCONS_ALL_GETTER_SETTER_AS, JSONCONS_ALL_GETTER_SETTER_TO_JSON,NumTemplateParams,ClassType,GetPrefix,SetPrefix, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__),__VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_GETTER_SETTER_NAME_IS(P1, P2, P3, Seq, Count) JSONCONS_GETTER_SETTER_NAME_IS_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_GETTER_SETTER_NAME_IS_LAST(P1, P2, P3, Seq, Count) if ((num_params-Count) < num_mandatory_params1 && JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_GETTER_SETTER_NAME_IS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_GETTER_SETTER_NAME_IS_3(Getter, Setter, Name) !ajson.contains(Name)) return false; +#define JSONCONS_GETTER_SETTER_NAME_IS_5(Getter, Setter, Name, Mode, Match) JSONCONS_GETTER_SETTER_NAME_IS_7(Getter, Setter, Name, Mode, Match,, ) +#define JSONCONS_GETTER_SETTER_NAME_IS_6(Getter, Setter, Name, Mode, Match, Into) JSONCONS_GETTER_SETTER_NAME_IS_7(Getter, Setter, Name, Mode, Match, Into, ) +#define JSONCONS_GETTER_SETTER_NAME_IS_7(Getter, Setter, Name, Mode, Match, Into, From) !ajson.contains(Name)) return false; \ + JSONCONS_TRY{if (!Match(ajson.at(Name).template as())->Getter()))>::type>())) return false;} \ + JSONCONS_CATCH(...) {return false;} + +#define JSONCONS_N_GETTER_SETTER_NAME_AS(P1, P2, P3, Seq, Count) JSONCONS_N_GETTER_SETTER_NAME_AS_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_N_GETTER_SETTER_NAME_AS_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_N_GETTER_SETTER_NAME_AS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_N_GETTER_SETTER_NAME_AS_3(Getter, Setter, Name) if (ajson.contains(Name)) class_instance.Setter(ajson.at(Name).template as::type>()); +#define JSONCONS_N_GETTER_SETTER_NAME_AS_4(Getter, Setter, Name, Mode) Mode(JSONCONS_N_GETTER_SETTER_NAME_AS_3(Getter, Setter, Name)) +#define JSONCONS_N_GETTER_SETTER_NAME_AS_5(Getter, Setter, Name, Mode, Match) JSONCONS_N_GETTER_SETTER_NAME_AS_7(Getter, Setter, Name, Mode, Match, , ) +#define JSONCONS_N_GETTER_SETTER_NAME_AS_6(Getter, Setter, Name, Mode, Match, Into) JSONCONS_N_GETTER_SETTER_NAME_AS_7(Getter, Setter, Name, Mode, Match, Into, ) +#define JSONCONS_N_GETTER_SETTER_NAME_AS_7(Getter, Setter, Name, Mode, Match, Into, From) Mode(if (ajson.contains(Name)) class_instance.Setter(From(ajson.at(Name).template as::type>()));) + +#define JSONCONS_N_GETTER_SETTER_NAME_TO_JSON(P1, P2, P3, Seq, Count) JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_3(Getter, Setter, Name) ajson.try_emplace(Name,class_instance.Getter() ); +#define JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_5(Getter, Setter, Name, Mode, Match) JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_7(Getter, Setter, Name, Mode, Match, , ) +#define JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_6(Getter, Setter, Name, Mode, Match, Into) JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_7(Getter, Setter, Name, Mode, Match, Into, ) +#define JSONCONS_N_GETTER_SETTER_NAME_TO_JSON_7(Getter, Setter, Name, Mode, Match, Into, From) ajson.try_emplace(Name, Into(class_instance.Getter()) ); + +#define JSONCONS_ALL_GETTER_SETTER_NAME_AS(P1, P2, P3, Seq, Count) JSONCONS_ALL_GETTER_SETTER_NAME_AS_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_ALL_GETTER_SETTER_NAME_AS_LAST(P1, P2, P3, Seq, Count) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_ALL_GETTER_SETTER_NAME_AS_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_ALL_GETTER_SETTER_NAME_AS_3(Getter, Setter, Name) class_instance.Setter(ajson.at(Name).template as::type>()); +#define JSONCONS_ALL_GETTER_SETTER_NAME_AS_4(Getter, Setter, Name, Mode) Mode(JSONCONS_ALL_GETTER_SETTER_NAME_AS_3(Getter, Setter, Name)) +#define JSONCONS_ALL_GETTER_SETTER_NAME_AS_5(Getter, Setter, Name, Mode, Match) JSONCONS_ALL_GETTER_SETTER_NAME_AS_7(Getter, Setter, Name, Mode, Match, , ) +#define JSONCONS_ALL_GETTER_SETTER_NAME_AS_6(Getter, Setter, Name, Mode, Match, Into) JSONCONS_ALL_GETTER_SETTER_NAME_AS_7(Getter, Setter, Name, Mode, Match, Into, ) +#define JSONCONS_ALL_GETTER_SETTER_NAME_AS_7(Getter, Setter, Name, Mode, Match, Into, From) Mode(class_instance.Setter(From(ajson.at(Name).template as::type>()));) + +#define JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON(P1, P2, P3, Seq, Count) JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) +#define JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_LAST(P1, P2, P3, Seq, Count) if ((num_params-Count) < num_mandatory_params2) JSONCONS_EXPAND(JSONCONS_CONCAT(JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_,JSONCONS_NARGS Seq) Seq) +#define JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_3(Getter, Setter, Name) \ + ajson.try_emplace(Name,class_instance.Getter()); \ +else \ + {json_traits_helper::set_optional_json_member(Name,class_instance.Getter(), ajson);} +#define JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_5(Getter, Setter, Name, Mode, Match) JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_7(Getter, Setter, Name, Mode, Match, , ) +#define JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_6(Getter, Setter, Name, Mode, Match, Into) JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_7(Getter, Setter, Name, Mode, Match, Into, ) +#define JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON_7(Getter, Setter, Name, Mode, Match, Into, From) \ + ajson.try_emplace(Name, Into(class_instance.Getter())); \ +else \ + {json_traits_helper::set_optional_json_member(Name, Into(class_instance.Getter()), ajson);} + +#define JSONCONS_GETTER_SETTER_NAME_TRAITS_BASE(AsT,ToJ, NumTemplateParams, ClassType,NumMandatoryParams1,NumMandatoryParams2, ...) \ +namespace jsoncons \ +{ \ + template \ + struct json_type_traits \ + { \ + using class_type = ClassType JSONCONS_GENERATE_TPL_ARGS(JSONCONS_GENERATE_TPL_ARG, NumTemplateParams); \ + using allocator_type = typename Json::allocator_type; \ + using char_type = typename Json::char_type; \ + using string_view_type = typename Json::string_view_type; \ + constexpr static size_t num_params = JSONCONS_NARGS(__VA_ARGS__); \ + constexpr static size_t num_mandatory_params1 = NumMandatoryParams1; \ + constexpr static size_t num_mandatory_params2 = NumMandatoryParams2; \ + static bool is(const Json& ajson) noexcept \ + { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_GETTER_SETTER_NAME_IS,,,, __VA_ARGS__)\ + return true; \ + } \ + static class_type as(const Json& ajson) \ + { \ + if (!is(ajson)) JSONCONS_THROW(conv_error(conv_errc::conversion_failed, "Not a " # ClassType)); \ + class_type class_instance{}; \ + JSONKONS_VARIADIC_FOR_EACH(AsT,,,, __VA_ARGS__) \ + return class_instance; \ + } \ + static Json to_json(const class_type& class_instance, allocator_type alloc=allocator_type()) \ + { \ + Json ajson(json_object_arg, semantic_tag::none, alloc); \ + JSONKONS_VARIADIC_FOR_EACH(ToJ,,,, __VA_ARGS__) \ + return ajson; \ + } \ + }; \ +} \ + /**/ + +#define JSONCONS_N_GETTER_SETTER_NAME_TRAITS(ClassType,NumMandatoryParams, ...) \ + JSONCONS_GETTER_SETTER_NAME_TRAITS_BASE(JSONCONS_N_GETTER_SETTER_NAME_AS,JSONCONS_N_GETTER_SETTER_NAME_TO_JSON, 0, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_N_GETTER_SETTER_NAME_TRAITS(NumTemplateParams, ClassType,NumMandatoryParams, ...) \ + JSONCONS_GETTER_SETTER_NAME_TRAITS_BASE(JSONCONS_N_GETTER_SETTER_NAME_AS,JSONCONS_N_GETTER_SETTER_NAME_TO_JSON, NumTemplateParams, ClassType,NumMandatoryParams,NumMandatoryParams, __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_ALL_GETTER_SETTER_NAME_TRAITS(ClassType, ...) \ + JSONCONS_GETTER_SETTER_NAME_TRAITS_BASE(JSONCONS_ALL_GETTER_SETTER_NAME_AS,JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON, 0, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template <> struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_TPL_ALL_GETTER_SETTER_NAME_TRAITS(NumTemplateParams, ClassType, ...) \ + JSONCONS_GETTER_SETTER_NAME_TRAITS_BASE(JSONCONS_ALL_GETTER_SETTER_NAME_AS,JSONCONS_ALL_GETTER_SETTER_NAME_TO_JSON, NumTemplateParams, ClassType, JSONCONS_NARGS(__VA_ARGS__), JSONCONS_NARGS(__VA_ARGS__), __VA_ARGS__) \ + namespace jsoncons { template struct is_json_type_traits_declared : public std::true_type {}; } \ + /**/ + +#define JSONCONS_POLYMORPHIC_IS(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return true; +#define JSONCONS_POLYMORPHIC_IS_LAST(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return true; + +#define JSONCONS_POLYMORPHIC_AS(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return std::make_shared(ajson.template as()); +#define JSONCONS_POLYMORPHIC_AS_LAST(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return std::make_shared(ajson.template as()); + +#define JSONCONS_POLYMORPHIC_AS_UNIQUE_PTR(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return jsoncons::make_unique(ajson.template as()); +#define JSONCONS_POLYMORPHIC_AS_UNIQUE_PTR_LAST(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return jsoncons::make_unique(ajson.template as()); + +#define JSONCONS_POLYMORPHIC_AS_SHARED_PTR(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return std::make_shared(ajson.template as()); +#define JSONCONS_POLYMORPHIC_AS_SHARED_PTR_LAST(BaseClass, P2, P3, DerivedClass, Count) if (ajson.template is()) return std::make_shared(ajson.template as()); + +#define JSONCONS_POLYMORPHIC_TO_JSON(BaseClass, P2, P3, DerivedClass, Count) if (DerivedClass* p = dynamic_cast(ptr.get())) {return Json(*p);} +#define JSONCONS_POLYMORPHIC_TO_JSON_LAST(BaseClass, P2, P3, DerivedClass, Count) if (DerivedClass* p = dynamic_cast(ptr.get())) {return Json(*p);} + +#define JSONCONS_POLYMORPHIC_TRAITS(BaseClass, ...) \ +namespace jsoncons { \ + template \ + struct json_type_traits> { \ + static bool is(const Json& ajson) noexcept { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_POLYMORPHIC_IS, BaseClass,,, __VA_ARGS__)\ + return false; \ + } \ +\ + static std::shared_ptr as(const Json& ajson) { \ + if (!ajson.is_object()) return std::shared_ptr(); \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_POLYMORPHIC_AS_SHARED_PTR, BaseClass,,, __VA_ARGS__)\ + return std::shared_ptr(); \ + } \ +\ + static Json to_json(const std::shared_ptr& ptr) { \ + if (ptr.get() == nullptr) {return Json::null();} \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_POLYMORPHIC_TO_JSON, BaseClass,,, __VA_ARGS__)\ + return Json::null(); \ + } \ + }; \ + template \ + struct json_type_traits> { \ + static bool is(const Json& ajson) noexcept { \ + if (!ajson.is_object()) return false; \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_POLYMORPHIC_IS, BaseClass,,, __VA_ARGS__)\ + return false; \ + } \ + static std::unique_ptr as(const Json& ajson) { \ + if (!ajson.is_object()) return std::unique_ptr(); \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_POLYMORPHIC_AS_UNIQUE_PTR, BaseClass,,, __VA_ARGS__)\ + return std::unique_ptr(); \ + } \ + static Json to_json(const std::unique_ptr& ptr) { \ + if (ptr.get() == nullptr) {return Json::null();} \ + JSONKONS_VARIADIC_FOR_EACH(JSONCONS_POLYMORPHIC_TO_JSON, BaseClass,,, __VA_ARGS__)\ + return Json::null(); \ + } \ + }; \ +} \ + /**/ + +#endif diff --git a/third_party/jsoncons/json_type.hpp b/third_party/jsoncons/json_type.hpp new file mode 100644 index 0000000000..04acd5d250 --- /dev/null +++ b/third_party/jsoncons/json_type.hpp @@ -0,0 +1,219 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_TYPE_HPP +#define JSONCONS_JSON_TYPE_HPP + +#include +#include + +namespace jsoncons { + + enum class json_type : uint8_t + { + null_value, + bool_value, + int64_value, + uint64_value, + half_value, + double_value, + string_value, + byte_string_value, + array_value, + object_value + }; + + template + std::basic_ostream& operator<<(std::basic_ostream& os, json_type type) + { + static constexpr const CharT* null_value = JSONCONS_CSTRING_CONSTANT(CharT, "null"); + static constexpr const CharT* bool_value = JSONCONS_CSTRING_CONSTANT(CharT, "bool"); + static constexpr const CharT* int64_value = JSONCONS_CSTRING_CONSTANT(CharT, "int64"); + static constexpr const CharT* uint64_value = JSONCONS_CSTRING_CONSTANT(CharT, "uint64"); + static constexpr const CharT* half_value = JSONCONS_CSTRING_CONSTANT(CharT, "half"); + static constexpr const CharT* double_value = JSONCONS_CSTRING_CONSTANT(CharT, "double"); + static constexpr const CharT* string_value = JSONCONS_CSTRING_CONSTANT(CharT, "string"); + static constexpr const CharT* byte_string_value = JSONCONS_CSTRING_CONSTANT(CharT, "byte_string"); + static constexpr const CharT* array_value = JSONCONS_CSTRING_CONSTANT(CharT, "array"); + static constexpr const CharT* object_value = JSONCONS_CSTRING_CONSTANT(CharT, "object"); + + switch (type) + { + case json_type::null_value: + { + os << null_value; + break; + } + case json_type::bool_value: + { + os << bool_value; + break; + } + case json_type::int64_value: + { + os << int64_value; + break; + } + case json_type::uint64_value: + { + os << uint64_value; + break; + } + case json_type::half_value: + { + os << half_value; + break; + } + case json_type::double_value: + { + os << double_value; + break; + } + case json_type::string_value: + { + os << string_value; + break; + } + case json_type::byte_string_value: + { + os << byte_string_value; + break; + } + case json_type::array_value: + { + os << array_value; + break; + } + case json_type::object_value: + { + os << object_value; + break; + } + } + return os; + } + + enum class json_storage_kind : uint8_t + { + null = 0, // 0000 + boolean = 1, // 0001 + int64 = 2, // 0010 + uint64 = 3, // 0011 + empty_object = 4, // 0100 + float64 = 5, // 0101 + half_float = 6, // 0110 + short_str = 7, // 0111 + const_json_pointer = 8, // 1000 + byte_str = 12, // 1100 + object = 13, // 1101 + array = 14, // 1110 + long_str = 15 // 1111 + }; + + inline bool is_string_storage(json_storage_kind storage_kind) noexcept + { + static const uint8_t mask{ uint8_t(json_storage_kind::short_str) & uint8_t(json_storage_kind::long_str) }; + return (uint8_t(storage_kind) & mask) == mask; + } + + inline bool is_scalar_storage(json_storage_kind storage_kind) noexcept + { + static const uint8_t mask{ uint8_t(json_storage_kind::long_str) & uint8_t(json_storage_kind::byte_str) + & uint8_t(json_storage_kind::array) & uint8_t(json_storage_kind::object) }; + return (uint8_t(storage_kind) & mask) != mask; + } + + template + std::basic_ostream& operator<<(std::basic_ostream& os, json_storage_kind storage) + { + static constexpr const CharT* null_value = JSONCONS_CSTRING_CONSTANT(CharT, "null"); + static constexpr const CharT* bool_value = JSONCONS_CSTRING_CONSTANT(CharT, "bool"); + static constexpr const CharT* int64_value = JSONCONS_CSTRING_CONSTANT(CharT, "int64"); + static constexpr const CharT* uint64_value = JSONCONS_CSTRING_CONSTANT(CharT, "uint64"); + static constexpr const CharT* half_value = JSONCONS_CSTRING_CONSTANT(CharT, "half"); + static constexpr const CharT* double_value = JSONCONS_CSTRING_CONSTANT(CharT, "double"); + static constexpr const CharT* short_string_value = JSONCONS_CSTRING_CONSTANT(CharT, "short_string"); + static constexpr const CharT* long_string_value = JSONCONS_CSTRING_CONSTANT(CharT, "string"); + static constexpr const CharT* byte_string_value = JSONCONS_CSTRING_CONSTANT(CharT, "byte_string"); + static constexpr const CharT* array_value = JSONCONS_CSTRING_CONSTANT(CharT, "array"); + static constexpr const CharT* empty_object_value = JSONCONS_CSTRING_CONSTANT(CharT, "empty_object"); + static constexpr const CharT* object_value = JSONCONS_CSTRING_CONSTANT(CharT, "object"); + static constexpr const CharT* const_json_pointer = JSONCONS_CSTRING_CONSTANT(CharT, "const_json_pointer"); + + switch (storage) + { + case json_storage_kind::null: + { + os << null_value; + break; + } + case json_storage_kind::boolean: + { + os << bool_value; + break; + } + case json_storage_kind::int64: + { + os << int64_value; + break; + } + case json_storage_kind::uint64: + { + os << uint64_value; + break; + } + case json_storage_kind::half_float: + { + os << half_value; + break; + } + case json_storage_kind::float64: + { + os << double_value; + break; + } + case json_storage_kind::short_str: + { + os << short_string_value; + break; + } + case json_storage_kind::long_str: + { + os << long_string_value; + break; + } + case json_storage_kind::byte_str: + { + os << byte_string_value; + break; + } + case json_storage_kind::array: + { + os << array_value; + break; + } + case json_storage_kind::empty_object: + { + os << empty_object_value; + break; + } + case json_storage_kind::object: + { + os << object_value; + break; + } + case json_storage_kind::const_json_pointer: + { + os << const_json_pointer; + break; + } + } + return os; + } + +} // jsoncons + +#endif diff --git a/third_party/jsoncons/json_type_traits.hpp b/third_party/jsoncons/json_type_traits.hpp new file mode 100644 index 0000000000..26a7b25dcb --- /dev/null +++ b/third_party/jsoncons/json_type_traits.hpp @@ -0,0 +1,1903 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_TYPE_TRAITS_HPP +#define JSONCONS_JSON_TYPE_TRAITS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include // std::swap +#include // std::numeric_limits +#include // std::enable_if +#include // std::iterator_traits, std::input_iterator_tag +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include // std::bitset +#include +#include + +#if defined(JSONCONS_HAS_STD_VARIANT) + #include +#endif + +namespace jsoncons { + + template + struct is_json_type_traits_declared : public std::false_type + {}; + + // json_type_traits + + template + struct unimplemented : std::false_type + {}; + + template + struct json_type_traits + { + using allocator_type = typename Json::allocator_type; + + static constexpr bool is_compatible = false; + + static constexpr bool is(const Json&) noexcept + { + return false; + } + + static T as(const Json&) + { + static_assert(unimplemented::value, "as not implemented"); + } + + static Json to_json(const T&) + { + static_assert(unimplemented::value, "to_json not implemented"); + } + + static Json to_json(const T&, const allocator_type&) + { + static_assert(unimplemented::value, "to_json not implemented"); + } + }; + +namespace detail { + +template +using +traits_can_convert_t = decltype(json_type_traits::can_convert(Json())); + +template +using +has_can_convert = extension_traits::is_detected; + + template + struct invoke_can_convert + { + template + static + typename std::enable_if::value,bool>::type + can_convert(const Json& j) noexcept + { + return json_type_traits::can_convert(j); + } + template + static + typename std::enable_if::value,bool>::type + can_convert(const Json& j) noexcept + { + return json_type_traits::is(j); + } + }; + + // is_json_type_traits_unspecialized + template + struct is_json_type_traits_unspecialized : std::false_type {}; + + // is_json_type_traits_unspecialized + template + struct is_json_type_traits_unspecialized::is_compatible>::value>::type + > : std::true_type {}; + + // is_compatible_array_type + template + struct is_compatible_array_type : std::false_type {}; + + template + struct is_compatible_array_type::value && + extension_traits::is_array_like::value && + !is_json_type_traits_unspecialized::value_type>::value + >::type> : std::true_type {}; + +} // namespace detail + + // is_json_type_traits_specialized + template + struct is_json_type_traits_specialized : std::false_type {}; + + template + struct is_json_type_traits_specialized::value + >::type> : std::true_type {}; + + template + struct json_type_traits::type*> + { + using char_type = typename Json::char_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_string(); + } + static const char_type* as(const Json& j) + { + return j.as_cstring(); + } + template + static Json to_json(const char_type* s, Args&&... args) + { + return Json(s, semantic_tag::none, std::forward(args)...); + } + }; + + template + struct json_type_traits::type*> + { + using char_type = typename Json::char_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_string(); + } + template + static Json to_json(const char_type* s, Args&&... args) + { + return Json(s, semantic_tag::none, std::forward(args)...); + } + }; + + // integer + + template + struct json_type_traits::value && sizeof(T) <= sizeof(int64_t)) || (extension_traits::is_unsigned_integer::value && sizeof(T) <= sizeof(uint64_t)) + >::type> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.template is_integer(); + } + static T as(const Json& j) + { + return j.template as_integer(); + } + + static Json to_json(T val) + { + return Json(val, semantic_tag::none); + } + + static Json to_json(T val, const allocator_type&) + { + return Json(val, semantic_tag::none); + } + }; + + template + struct json_type_traits::value && sizeof(T) > sizeof(int64_t)) || (extension_traits::is_unsigned_integer::value && sizeof(T) > sizeof(uint64_t)) + >::type> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.template is_integer(); + } + static T as(const Json& j) + { + return j.template as_integer(); + } + + static Json to_json(T val, const allocator_type& alloc = allocator_type()) + { + return Json(val, semantic_tag::none, alloc); + } + }; + + template + struct json_type_traits::value + >::type> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_double(); + } + static T as(const Json& j) + { + return static_cast(j.as_double()); + } + static Json to_json(T val) + { + return Json(val, semantic_tag::none); + } + static Json to_json(T val, const allocator_type&) + { + return Json(val, semantic_tag::none); + } + }; + + template + struct json_type_traits + { + using json_object = typename Json::object; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_object(); + } + static Json to_json(const json_object& o) + { + return Json(o,semantic_tag::none); + } + static Json to_json(const json_object& o, const allocator_type&) + { + return Json(o,semantic_tag::none); + } + }; + + template + struct json_type_traits + { + using json_array = typename Json::array; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_array(); + } + static Json to_json(const json_array& a) + { + return Json(a, semantic_tag::none); + } + static Json to_json(const json_array& a, const allocator_type&) + { + return Json(a, semantic_tag::none); + } + }; + + template + struct json_type_traits + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json&) noexcept + { + return true; + } + static Json as(Json j) + { + return j; + } + static Json to_json(const Json& val) + { + return val; + } + static Json to_json(const Json& val, const allocator_type&) + { + return val; + } + }; + + template + struct json_type_traits + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_null(); + } + static typename jsoncons::null_type as(const Json& j) + { + if (!j.is_null()) + { + JSONCONS_THROW(conv_error(conv_errc::not_jsoncons_null_type)); + } + return jsoncons::null_type(); + } + static Json to_json(jsoncons::null_type) + { + return Json(jsoncons::null_type{}, semantic_tag::none); + } + static Json to_json(jsoncons::null_type, const allocator_type&) + { + return Json(jsoncons::null_type{}, semantic_tag::none); + } + }; + + template + struct json_type_traits + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_bool(); + } + static bool as(const Json& j) + { + return j.as_bool(); + } + static Json to_json(bool val) + { + return Json(val, semantic_tag::none); + } + static Json to_json(bool val, const allocator_type&) + { + return Json(val, semantic_tag::none); + } + }; + + template + struct json_type_traits::const_reference>::value, + std::vector::const_reference, + void>::type>::value>::type> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_bool(); + } + static bool as(const Json& j) + { + return j.as_bool(); + } + static Json to_json(bool val) + { + return Json(val, semantic_tag::none); + } + static Json to_json(bool val, const allocator_type&) + { + return Json(val, semantic_tag::none); + } + }; + + template + struct json_type_traits::reference> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_bool(); + } + static bool as(const Json& j) + { + return j.as_bool(); + } + static Json to_json(bool val) + { + return Json(val, semantic_tag::none); + } + static Json to_json(bool val, const allocator_type&) + { + return Json(val, semantic_tag::none); + } + }; + + template + struct json_type_traits::value && + extension_traits::is_string::value && + std::is_same::value>::type> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_string(); + } + + static T as(const Json& j) + { + return T(j.as_string()); + } + + static Json to_json(const T& val) + { + return Json(val, semantic_tag::none); + } + + static Json to_json(const T& val, const allocator_type& alloc) + { + return Json(val, semantic_tag::none, alloc); + } + }; + + template + struct json_type_traits::value && + extension_traits::is_string::value && + !std::is_same::value>::type> + { + using char_type = typename Json::char_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_string(); + } + + static T as(const Json& j) + { + auto s = j.as_string(); + T val; + unicode_traits::convert(s.data(), s.size(), val); + return val; + } + + static Json to_json(const T& val) + { + std::basic_string s; + unicode_traits::convert(val.data(), val.size(), s); + + return Json(s, semantic_tag::none); + } + + static Json to_json(const T& val, const allocator_type& alloc) + { + std::basic_string s; + unicode_traits::convert(val.data(), val.size(), s); + return Json(s, semantic_tag::none, alloc); + } + }; + + template + struct json_type_traits::value && + extension_traits::is_string_view::value && + std::is_same::value>::type> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_string_view(); + } + + static T as(const Json& j) + { + return T(j.as_string_view().data(),j.as_string_view().size()); + } + + static Json to_json(const T& val) + { + return Json(val, semantic_tag::none); + } + + static Json to_json(const T& val, const allocator_type& alloc) + { + return Json(val, semantic_tag::none, alloc); + } + }; + + // array back insertable + + template + struct json_type_traits::value && + jsoncons::detail::is_compatible_array_type::value && + extension_traits::is_back_insertable::value + >::type> + { + typedef typename std::iterator_traits::value_type value_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + bool result = j.is_array(); + if (result) + { + for (auto e : j.array_range()) + { + if (!e.template is()) + { + result = false; + break; + } + } + } + return result; + } + + // array back insertable non-byte container + + template + static typename std::enable_if::value,Container>::type + as(const Json& j) + { + if (j.is_array()) + { + T result; + visit_reserve_(typename std::integral_constant::value>::type(),result,j.size()); + for (const auto& item : j.array_range()) + { + result.push_back(item.template as()); + } + + return result; + } + else + { + JSONCONS_THROW(conv_error(conv_errc::not_vector)); + } + } + + // array back insertable byte container + + template + static typename std::enable_if::value,Container>::type + as(const Json& j) + { + std::error_code ec; + if (j.is_array()) + { + T result; + visit_reserve_(typename std::integral_constant::value>::type(),result,j.size()); + for (const auto& item : j.array_range()) + { + result.push_back(item.template as()); + } + + return result; + } + else if (j.is_byte_string_view()) + { + value_converter converter; + auto v = converter.convert(j.as_byte_string_view(),j.tag(), ec); + if (ec) + { + JSONCONS_THROW(conv_error(ec)); + } + return v; + } + else if (j.is_string()) + { + value_converter,T> converter; + auto v = converter.convert(j.as_string_view(),j.tag(), ec); + if (ec) + { + JSONCONS_THROW(conv_error(ec)); + } + return v; + } + else + { + JSONCONS_THROW(conv_error(conv_errc::not_vector)); + } + } + + template + static typename std::enable_if::value,Json>::type + to_json(const T& val) + { + Json j(json_array_arg); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first,last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + + template + static typename std::enable_if::value,Json>::type + to_json(const T& val, const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first, last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + + template + static typename std::enable_if::value,Json>::type + to_json(const T& val) + { + Json j(byte_string_arg, val); + return j; + } + + template + static typename std::enable_if::value,Json>::type + to_json(const T& val, const allocator_type& alloc) + { + Json j(byte_string_arg, val, semantic_tag::none, alloc); + return j; + } + + static void visit_reserve_(std::true_type, T& v, std::size_t size) + { + v.reserve(size); + } + + static void visit_reserve_(std::false_type, T&, std::size_t) + { + } + }; + + // array, not back insertable but insertable + + template + struct json_type_traits::value && + jsoncons::detail::is_compatible_array_type::value && + !extension_traits::is_back_insertable::value && + extension_traits::is_insertable::value>::type> + { + typedef typename std::iterator_traits::value_type value_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + bool result = j.is_array(); + if (result) + { + for (auto e : j.array_range()) + { + if (!e.template is()) + { + result = false; + break; + } + } + } + return result; + } + + static T as(const Json& j) + { + if (j.is_array()) + { + T result; + for (const auto& item : j.array_range()) + { + result.insert(item.template as()); + } + + return result; + } + else + { + JSONCONS_THROW(conv_error(conv_errc::not_vector)); + } + } + + static Json to_json(const T& val) + { + Json j(json_array_arg); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first,last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + + static Json to_json(const T& val, const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first, last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + }; + + // array not back insertable or insertable, but front insertable + + template + struct json_type_traits::value && + jsoncons::detail::is_compatible_array_type::value && + !extension_traits::is_back_insertable::value && + !extension_traits::is_insertable::value && + extension_traits::is_front_insertable::value>::type> + { + typedef typename std::iterator_traits::value_type value_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + bool result = j.is_array(); + if (result) + { + for (auto e : j.array_range()) + { + if (!e.template is()) + { + result = false; + break; + } + } + } + return result; + } + + static T as(const Json& j) + { + if (j.is_array()) + { + T result; + + auto it = j.array_range().rbegin(); + auto end = j.array_range().rend(); + for (; it != end; ++it) + { + result.push_front((*it).template as()); + } + + return result; + } + else + { + JSONCONS_THROW(conv_error(conv_errc::not_vector)); + } + } + + static Json to_json(const T& val) + { + Json j(json_array_arg); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first,last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + + static Json to_json(const T& val, const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first, last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + }; + + // std::array + + template + struct json_type_traits> + { + using allocator_type = typename Json::allocator_type; + + using value_type = E; + + static bool is(const Json& j) noexcept + { + bool result = j.is_array() && j.size() == N; + if (result) + { + for (auto e : j.array_range()) + { + if (!e.template is()) + { + result = false; + break; + } + } + } + return result; + } + + static std::array as(const Json& j) + { + std::array buff; + if (j.size() != N) + { + JSONCONS_THROW(conv_error(conv_errc::not_array)); + } + for (std::size_t i = 0; i < N; i++) + { + buff[i] = j[i].template as(); + } + return buff; + } + + static Json to_json(const std::array& val) + { + Json j(json_array_arg); + j.reserve(N); + for (auto it = val.begin(); it != val.end(); ++it) + { + j.push_back(*it); + } + return j; + } + + static Json to_json(const std::array& val, + const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + j.reserve(N); + for (auto it = val.begin(); it != val.end(); ++it) + { + j.push_back(*it); + } + return j; + } + }; + + // map like + template + struct json_type_traits::value && + extension_traits::is_map_like::value && + extension_traits::is_constructible_from_const_pointer_and_size::value && + is_json_type_traits_specialized::value>::type + > + { + using mapped_type = typename T::mapped_type; + using value_type = typename T::value_type; + using key_type = typename T::key_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + bool result = j.is_object(); + for (auto member : j.object_range()) + { + if (!member.value().template is()) + { + result = false; + } + } + return result; + } + + static T as(const Json& j) + { + if (!j.is_object()) + { + JSONCONS_THROW(conv_error(conv_errc::not_map)); + } + T result; + for (const auto& item : j.object_range()) + { + result.emplace(key_type(item.key().data(),item.key().size()), item.value().template as()); + } + + return result; + } + + static Json to_json(const T& val) + { + Json j(json_object_arg, val.begin(), val.end()); + return j; + } + + static Json to_json(const T& val, const allocator_type& alloc) + { + Json j(json_object_arg, val.begin(), val.end(), alloc); + return j; + } + }; + + template + struct json_type_traits::value && + extension_traits::is_map_like::value && + !extension_traits::is_constructible_from_const_pointer_and_size::value && + is_json_type_traits_specialized::value && + is_json_type_traits_specialized::value>::type + > + { + using mapped_type = typename T::mapped_type; + using value_type = typename T::value_type; + using key_type = typename T::key_type; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& val) noexcept + { + if (!val.is_object()) + return false; + for (const auto& item : val.object_range()) + { + Json j(item.key()); + if (!j.template is()) + { + return false; + } + if (!item.value().template is()) + { + return false; + } + } + return true; + } + + static T as(const Json& val) + { + T result; + for (const auto& item : val.object_range()) + { + Json j(item.key()); + auto key = json_type_traits::as(j); + result.emplace(std::move(key), item.value().template as()); + } + + return result; + } + + static Json to_json(const T& val) + { + Json j(json_object_arg); + j.reserve(val.size()); + for (const auto& item : val) + { + auto temp = json_type_traits::to_json(item.first); + if (temp.is_string_view()) + { + j.try_emplace(typename Json::key_type(temp.as_string_view()), item.second); + } + else + { + typename Json::key_type key; + temp.dump(key); + j.try_emplace(std::move(key), item.second); + } + } + return j; + } + + static Json to_json(const T& val, const allocator_type& alloc) + { + Json j(json_object_arg, semantic_tag::none, alloc); + j.reserve(val.size()); + for (const auto& item : val) + { + auto temp = json_type_traits::to_json(item.first, alloc); + if (temp.is_string_view()) + { + j.try_emplace(typename Json::key_type(temp.as_string_view(), alloc), item.second); + } + else + { + typename Json::key_type key(alloc); + temp.dump(key); + j.try_emplace(std::move(key), item.second, alloc); + } + } + return j; + } + }; + + namespace tuple_detail + { + template + struct json_tuple_helper + { + using element_type = typename std::tuple_element::type; + using next = json_tuple_helper; + + static bool is(const Json& j) noexcept + { + if (j[Size-Pos].template is()) + { + return next::is(j); + } + else + { + return false; + } + } + + static void as(Tuple& tuple, const Json& j) + { + std::get(tuple) = j[Size-Pos].template as(); + next::as(tuple, j); + } + + static void to_json(const Tuple& tuple, Json& j) + { + j.push_back(json_type_traits::to_json(std::get(tuple))); + next::to_json(tuple, j); + } + }; + + template + struct json_tuple_helper<0, Size, Json, Tuple> + { + static bool is(const Json&) noexcept + { + return true; + } + + static void as(Tuple&, const Json&) + { + } + + static void to_json(const Tuple&, Json&) + { + } + }; + } // namespace detail + + template + struct json_type_traits> + { + private: + using helper = tuple_detail::json_tuple_helper>; + + public: + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return helper::is(j); + } + + static std::tuple as(const Json& j) + { + std::tuple buff; + helper::as(buff, j); + return buff; + } + + static Json to_json(const std::tuple& val) + { + Json j(json_array_arg); + j.reserve(sizeof...(E)); + helper::to_json(val, j); + return j; + } + + static Json to_json(const std::tuple& val, + const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + j.reserve(sizeof...(E)); + helper::to_json(val, j); + return j; + } + }; + + template + struct json_type_traits> + { + public: + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_array() && j.size() == 2; + } + + static std::pair as(const Json& j) + { + return std::make_pair(j[0].template as(),j[1].template as()); + } + + static Json to_json(const std::pair& val) + { + Json j(json_array_arg); + j.reserve(2); + j.push_back(val.first); + j.push_back(val.second); + return j; + } + + static Json to_json(const std::pair& val, const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + j.reserve(2); + j.push_back(val.first); + j.push_back(val.second); + return j; + } + }; + + template + struct json_type_traits::value>::type> + { + public: + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_byte_string(); + } + + static T as(const Json& j) + { + return j.template as_byte_string(); + } + + static Json to_json(const T& val, + const allocator_type& alloc = allocator_type()) + { + return Json(byte_string_arg, val, semantic_tag::none, alloc); + } + }; + + template + struct json_type_traits, + typename std::enable_if>::value && + !std::is_polymorphic::value + >::type> + { + static bool is(const Json& j) noexcept + { + return j.is_null() || j.template is(); + } + + static std::shared_ptr as(const Json& j) + { + return j.is_null() ? std::shared_ptr(nullptr) : std::make_shared(j.template as()); + } + + static Json to_json(const std::shared_ptr& ptr) + { + if (ptr.get() != nullptr) + { + Json j(*ptr); + return j; + } + else + { + return Json::null(); + } + } + }; + + template + struct json_type_traits, + typename std::enable_if>::value && + !std::is_polymorphic::value + >::type> + { + static bool is(const Json& j) noexcept + { + return j.is_null() || j.template is(); + } + + static std::unique_ptr as(const Json& j) + { + return j.is_null() ? std::unique_ptr(nullptr) : jsoncons::make_unique(j.template as()); + } + + static Json to_json(const std::unique_ptr& ptr) + { + if (ptr.get() != nullptr) + { + Json j(*ptr); + return j; + } + else + { + return Json::null(); + } + } + }; + + template + struct json_type_traits, + typename std::enable_if>::value>::type> + { + public: + static bool is(const Json& j) noexcept + { + return j.is_null() || j.template is(); + } + + static jsoncons::optional as(const Json& j) + { + return j.is_null() ? jsoncons::optional() : jsoncons::optional(j.template as()); + } + + static Json to_json(const jsoncons::optional& val) + { + return val.has_value() ? Json(*val) : Json::null(); + } + }; + + template + struct json_type_traits + { + using allocator_type = typename Json::allocator_type; + + public: + static bool is(const Json& j) noexcept + { + return j.is_byte_string_view(); + } + + static byte_string_view as(const Json& j) + { + return j.as_byte_string_view(); + } + + static Json to_json(const byte_string_view& val, const allocator_type& alloc = allocator_type()) + { + return Json(byte_string_arg, val, semantic_tag::none, alloc); + } + }; + + // basic_bigint + + template + struct json_type_traits> + { + public: + using char_type = typename Json::char_type; + + static bool is(const Json& j) noexcept + { + switch (j.type()) + { + case json_type::string_value: + return jsoncons::detail::is_base10(j.as_string_view().data(), j.as_string_view().length()); + case json_type::int64_value: + case json_type::uint64_value: + return true; + default: + return false; + } + } + + static basic_bigint as(const Json& j) + { + switch (j.type()) + { + case json_type::string_value: + if (!jsoncons::detail::is_base10(j.as_string_view().data(), j.as_string_view().length())) + { + JSONCONS_THROW(conv_error(conv_errc::not_bigint)); + } + return basic_bigint::from_string(j.as_string_view().data(), j.as_string_view().length()); + case json_type::half_value: + case json_type::double_value: + return basic_bigint(j.template as()); + case json_type::int64_value: + return basic_bigint(j.template as()); + case json_type::uint64_value: + return basic_bigint(j.template as()); + default: + JSONCONS_THROW(conv_error(conv_errc::not_bigint)); + } + } + + static Json to_json(const basic_bigint& val) + { + std::basic_string s; + val.write_string(s); + return Json(s,semantic_tag::bigint); + } + }; + + // std::valarray + + template + struct json_type_traits> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + bool result = j.is_array(); + if (result) + { + for (auto e : j.array_range()) + { + if (!e.template is()) + { + result = false; + break; + } + } + } + return result; + } + + static std::valarray as(const Json& j) + { + if (j.is_array()) + { + std::valarray v(j.size()); + for (std::size_t i = 0; i < j.size(); ++i) + { + v[i] = j[i].template as(); + } + return v; + } + else + { + JSONCONS_THROW(conv_error(conv_errc::not_array)); + } + } + + static Json to_json(const std::valarray& val) + { + Json j(json_array_arg); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first,last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + + static Json to_json(const std::valarray& val, const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + auto first = std::begin(val); + auto last = std::end(val); + std::size_t size = std::distance(first,last); + j.reserve(size); + for (auto it = first; it != last; ++it) + { + j.push_back(*it); + } + return j; + } + }; + +#if defined(JSONCONS_HAS_STD_VARIANT) + +namespace variant_detail +{ + template + typename std::enable_if, bool>::type + is_variant(const Json& /*j*/) + { + return false; + } + + template + typename std::enable_if, bool>::type + is_variant(const Json& j) + { + if (j.template is()) + { + return true; + } + else + { + return is_variant(j); + } + } + + template + typename std::enable_if, Variant>::type + as_variant(const Json& /*j*/) + { + JSONCONS_THROW(conv_error(conv_errc::not_variant)); + } + + template + typename std::enable_if, Variant>::type + as_variant(const Json& j) + { + if (j.template is()) + { + Variant var(j.template as()); + return var; + } + else + { + return as_variant(j); + } + } + + template + struct variant_to_json_visitor + { + Json& j_; + + variant_to_json_visitor(Json& j) : j_(j) {} + + template + void operator()(const T& value) const + { + j_ = value; + } + }; + +} // namespace variant_detail + + template + struct json_type_traits> + { + public: + using variant_type = typename std::variant; + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return variant_detail::is_variant<0,Json,variant_type, VariantTypes...>(j); + } + + static std::variant as(const Json& j) + { + return variant_detail::as_variant<0,Json,variant_type, VariantTypes...>(j); + } + + static Json to_json(const std::variant& var) + { + Json j(json_array_arg); + variant_detail::variant_to_json_visitor visitor(j); + std::visit(visitor, var); + return j; + } + + static Json to_json(const std::variant& var, + const allocator_type& alloc) + { + Json j(json_array_arg, alloc); + variant_detail::variant_to_json_visitor visitor(j); + std::visit(visitor, var); + return j; + } + }; +#endif + + // std::chrono::duration + template + struct json_type_traits> + { + using duration_type = std::chrono::duration; + + using allocator_type = typename Json::allocator_type; + + static constexpr int64_t nanos_in_milli = 1000000; + static constexpr int64_t nanos_in_second = 1000000000; + static constexpr int64_t millis_in_second = 1000; + + static bool is(const Json& j) noexcept + { + return (j.tag() == semantic_tag::epoch_second || j.tag() == semantic_tag::epoch_milli || j.tag() == semantic_tag::epoch_nano); + } + + static duration_type as(const Json& j) + { + return from_json_(j); + } + + static Json to_json(const duration_type& val, const allocator_type& = allocator_type()) + { + return to_json_(val); + } + + template + static + typename std::enable_if>::value, duration_type>::type + from_json_(const Json& j) + { + if (j.is_int64() || j.is_uint64() || j.is_double()) + { + auto count = j.template as(); + switch (j.tag()) + { + case semantic_tag::epoch_second: + return duration_type(count); + case semantic_tag::epoch_milli: + return duration_type(count == 0 ? 0 : count/millis_in_second); + case semantic_tag::epoch_nano: + return duration_type(count == 0 ? 0 : count/nanos_in_second); + default: + return duration_type(count); + } + } + else if (j.is_string()) + { + switch (j.tag()) + { + case semantic_tag::epoch_second: + { + auto count = j.template as(); + return duration_type(count); + } + case semantic_tag::epoch_milli: + { + auto sv = j.as_string_view(); + bigint n = bigint::from_string(sv.data(), sv.length()); + if (n != 0) + { + n = n / millis_in_second; + } + return duration_type(static_cast(n)); + } + case semantic_tag::epoch_nano: + { + auto sv = j.as_string_view(); + bigint n = bigint::from_string(sv.data(), sv.length()); + if (n != 0) + { + n = n / nanos_in_second; + } + return duration_type(static_cast(n)); + } + default: + { + auto count = j.template as(); + return duration_type(count); + } + } + } + else + { + return duration_type(); + } + } + + template + static + typename std::enable_if::value, duration_type>::type + from_json_(const Json& j) + { + if (j.is_int64() || j.is_uint64()) + { + auto count = j.template as(); + switch (j.tag()) + { + case semantic_tag::epoch_second: + return duration_type(count*millis_in_second); + case semantic_tag::epoch_milli: + return duration_type(count); + case semantic_tag::epoch_nano: + return duration_type(count == 0 ? 0 : count/nanos_in_milli); + default: + return duration_type(count); + } + } + else if (j.is_double()) + { + auto count = j.template as(); + switch (j.tag()) + { + case semantic_tag::epoch_second: + return duration_type(static_cast(count * millis_in_second)); + case semantic_tag::epoch_milli: + return duration_type(static_cast(count)); + case semantic_tag::epoch_nano: + return duration_type(count == 0 ? 0 : static_cast(count / nanos_in_milli)); + default: + return duration_type(static_cast(count)); + } + } + else if (j.is_string()) + { + switch (j.tag()) + { + case semantic_tag::epoch_second: + { + auto count = j.template as(); + return duration_type(count*millis_in_second); + } + case semantic_tag::epoch_milli: + { + auto sv = j.as_string_view(); + Rep n{0}; + auto result = jsoncons::detail::decimal_to_integer(sv.data(), sv.size(), n); + if (!result) + { + return duration_type(); + } + return duration_type(n); + } + case semantic_tag::epoch_nano: + { + auto sv = j.as_string_view(); + bigint n = bigint::from_string(sv.data(), sv.length()); + if (n != 0) + { + n = n / nanos_in_milli; + } + return duration_type(static_cast(n)); + } + default: + { + auto count = j.template as(); + return duration_type(count); + } + } + } + else + { + return duration_type(); + } + } + + template + static + typename std::enable_if::value, duration_type>::type + from_json_(const Json& j) + { + if (j.is_int64() || j.is_uint64() || j.is_double()) + { + auto count = j.template as(); + switch (j.tag()) + { + case semantic_tag::epoch_second: + return duration_type(count*nanos_in_second); + case semantic_tag::epoch_milli: + return duration_type(count*nanos_in_milli); + case semantic_tag::epoch_nano: + return duration_type(count); + default: + return duration_type(count); + } + } + else if (j.is_double()) + { + auto count = j.template as(); + switch (j.tag()) + { + case semantic_tag::epoch_second: + return duration_type(static_cast(count * nanos_in_second)); + case semantic_tag::epoch_milli: + return duration_type(static_cast(count * nanos_in_milli)); + case semantic_tag::epoch_nano: + return duration_type(static_cast(count)); + default: + return duration_type(static_cast(count)); + } + } + else if (j.is_string()) + { + auto count = j.template as(); + switch (j.tag()) + { + case semantic_tag::epoch_second: + return duration_type(count*nanos_in_second); + case semantic_tag::epoch_milli: + return duration_type(count*nanos_in_milli); + case semantic_tag::epoch_nano: + return duration_type(count); + default: + return duration_type(count); + } + } + else + { + return duration_type(); + } + } + + template + static + typename std::enable_if>::value,Json>::type + to_json_(const duration_type& val) + { + return Json(val.count(), semantic_tag::epoch_second); + } + + template + static + typename std::enable_if::value,Json>::type + to_json_(const duration_type& val) + { + return Json(val.count(), semantic_tag::epoch_milli); + } + + template + static + typename std::enable_if::value,Json>::type + to_json_(const duration_type& val) + { + return Json(val.count(), semantic_tag::epoch_nano); + } + }; + + // std::nullptr_t + template + struct json_type_traits + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + return j.is_null(); + } + + static std::nullptr_t as(const Json& j) + { + if (!j.is_null()) + { + JSONCONS_THROW(conv_error(conv_errc::not_nullptr)); + } + return nullptr; + } + + static Json to_json(const std::nullptr_t&, const allocator_type& = allocator_type()) + { + return Json::null(); + } + }; + + // std::bitset + + struct null_back_insertable_byte_container + { + using value_type = uint8_t; + + void push_back(value_type) + { + } + }; + + template + struct json_type_traits> + { + using allocator_type = typename Json::allocator_type; + + static bool is(const Json& j) noexcept + { + if (j.is_byte_string()) + { + return true; + } + else if (j.is_string()) + { + jsoncons::string_view sv = j.as_string_view(); + null_back_insertable_byte_container cont; + auto result = decode_base16(sv.begin(), sv.end(), cont); + return result.ec == conv_errc::success ? true : false; + } + return false; + } + + static std::bitset as(const Json& j) + { + if (j.template is()) + { + auto bits = j.template as(); + std::bitset bs = static_cast(bits); + return bs; + } + else if (j.is_byte_string() || j.is_string()) + { + std::bitset bs; + std::vector bits; + if (j.is_byte_string()) + { + bits = j.template as>(); + } + else + { + jsoncons::string_view sv = j.as_string_view(); + auto result = decode_base16(sv.begin(), sv.end(), bits); + if (result.ec != conv_errc::success) + { + JSONCONS_THROW(conv_error(conv_errc::not_bitset)); + } + } + std::uint8_t byte = 0; + std::uint8_t mask = 0; + + std::size_t pos = 0; + for (std::size_t i = 0; i < N; ++i) + { + if (mask == 0) + { + if (pos >= bits.size()) + { + JSONCONS_THROW(conv_error(conv_errc::not_bitset)); + } + byte = bits.at(pos++); + mask = 0x80; + } + + if (byte & mask) + { + bs[i] = 1; + } + + mask = static_cast(mask >> 1); + } + return bs; + } + else + { + JSONCONS_THROW(conv_error(conv_errc::not_bitset)); + } + } + + static Json to_json(const std::bitset& val, + const allocator_type& alloc = allocator_type()) + { + std::vector bits; + + uint8_t byte = 0; + uint8_t mask = 0x80; + + for (std::size_t i = 0; i < N; ++i) + { + if (val[i]) + { + byte |= mask; + } + + mask = static_cast(mask >> 1); + + if (mask == 0) + { + bits.push_back(byte); + byte = 0; + mask = 0x80; + } + } + + // Encode remainder + if (mask != 0x80) + { + bits.push_back(byte); + } + + Json j(byte_string_arg, bits, semantic_tag::base16, alloc); + return j; + } + }; + +} // jsoncons + +#endif diff --git a/third_party/jsoncons/json_visitor.hpp b/third_party/jsoncons/json_visitor.hpp new file mode 100644 index 0000000000..3e9f23bd7c --- /dev/null +++ b/third_party/jsoncons/json_visitor.hpp @@ -0,0 +1,1067 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSON_VISITOR_HPP +#define JSONCONS_JSON_VISITOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + + template + class basic_json_visitor + { + public: + using char_type = CharT; + using char_traits_type = std::char_traits; + + using string_view_type = jsoncons::basic_string_view; + + basic_json_visitor() = default; + + virtual ~basic_json_visitor() noexcept = default; + + void flush() + { + visit_flush(); + } + + bool begin_object(semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_object(tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_object(std::size_t length, + semantic_tag tag=semantic_tag::none, + const ser_context& context = ser_context()) + { + std::error_code ec; + bool more = visit_begin_object(length, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool end_object(const ser_context& context = ser_context()) + { + std::error_code ec; + bool more = visit_end_object(context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_array(semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_array(tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_array(std::size_t length, + semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_array(length, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool end_array(const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_end_array(context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool key(const string_view_type& name, const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_key(name, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool null_value(semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_null(tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool bool_value(bool value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_bool(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool string_value(const string_view_type& value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_string(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + template + bool byte_string_value(const Source& b, + semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context(), + typename std::enable_if::value,int>::type = 0) + { + std::error_code ec; + bool more = visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + template + bool byte_string_value(const Source& b, + uint64_t ext_tag, + const ser_context& context=ser_context(), + typename std::enable_if::value,int>::type = 0) + { + std::error_code ec; + bool more = visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), ext_tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool uint64_value(uint64_t value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_uint64(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool int64_value(int64_t value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_int64(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool half_value(uint16_t value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_half(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool double_value(double value, + semantic_tag tag = semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_double(value, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_object(semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_object(tag, context, ec); + } + + bool begin_object(std::size_t length, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_object(length, tag, context, ec); + } + + bool end_object(const ser_context& context, std::error_code& ec) + { + return visit_end_object(context, ec); + } + + bool begin_array(semantic_tag tag, const ser_context& context, std::error_code& ec) + { + return visit_begin_array(tag, context, ec); + } + + bool begin_array(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code& ec) + { + return visit_begin_array(length, tag, context, ec); + } + + bool end_array(const ser_context& context, std::error_code& ec) + { + return visit_end_array(context, ec); + } + + bool key(const string_view_type& name, const ser_context& context, std::error_code& ec) + { + return visit_key(name, context, ec); + } + + bool null_value(semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_null(tag, context, ec); + } + + bool bool_value(bool value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_bool(value, tag, context, ec); + } + + bool string_value(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_string(value, tag, context, ec); + } + + template + bool byte_string_value(const Source& b, + semantic_tag tag, + const ser_context& context, + std::error_code& ec, + typename std::enable_if::value,int>::type = 0) + { + return visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), tag, context, ec); + } + + template + bool byte_string_value(const Source& b, + uint64_t ext_tag, + const ser_context& context, + std::error_code& ec, + typename std::enable_if::value,int>::type = 0) + { + return visit_byte_string(byte_string_view(reinterpret_cast(b.data()),b.size()), ext_tag, context, ec); + } + + bool uint64_value(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_uint64(value, tag, context, ec); + } + + bool int64_value(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_int64(value, tag, context, ec); + } + + bool half_value(uint16_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_half(value, tag, context, ec); + } + + bool double_value(double value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_double(value, tag, context, ec); + } + + template + bool typed_array(const jsoncons::span& data, + semantic_tag tag=semantic_tag::none, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_typed_array(data, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + template + bool typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_typed_array(data, tag, context, ec); + } + + bool typed_array(half_arg_t, const jsoncons::span& s, + semantic_tag tag = semantic_tag::none, + const ser_context& context = ser_context()) + { + std::error_code ec; + bool more = visit_typed_array(half_arg, s, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool typed_array(half_arg_t, const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_typed_array(half_arg, s, tag, context, ec); + } + + bool begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag = semantic_tag::multi_dim_row_major, + const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_begin_multi_dim(shape, tag, context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_multi_dim(shape, tag, context, ec); + } + + bool end_multi_dim(const ser_context& context=ser_context()) + { + std::error_code ec; + bool more = visit_end_multi_dim(context, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, context.line(), context.column())); + } + return more; + } + + bool end_multi_dim(const ser_context& context, + std::error_code& ec) + { + return visit_end_multi_dim(context, ec); + } + + private: + + virtual void visit_flush() = 0; + + virtual bool visit_begin_object(semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_begin_object(std::size_t /*length*/, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_object(tag, context, ec); + } + + virtual bool visit_end_object(const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_begin_array(semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_begin_array(std::size_t /*length*/, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_begin_array(tag, context, ec); + } + + virtual bool visit_end_array(const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_key(const string_view_type& name, + const ser_context& context, + std::error_code&) = 0; + + virtual bool visit_null(semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_bool(bool value, + semantic_tag tag, + const ser_context& context, + std::error_code&) = 0; + + virtual bool visit_string(const string_view_type& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_byte_string(const byte_string_view& value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_byte_string(const byte_string_view& value, + uint64_t /* ext_tag */, + const ser_context& context, + std::error_code& ec) + { + return visit_byte_string(value, semantic_tag::none, context, ec); + } + + virtual bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_half(uint16_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + return visit_double(binary::decode_half(value), + tag, + context, + ec); + } + + virtual bool visit_double(double value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) = 0; + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = uint64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(half_arg_t, + const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag, context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = half_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = double_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_typed_array(const jsoncons::span& s, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = begin_array(s.size(), tag,context, ec); + for (auto p = s.begin(); more && p != s.end(); ++p) + { + more = double_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = end_array(context, ec); + } + return more; + } + + virtual bool visit_begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) + { + bool more = visit_begin_array(2, tag, context, ec); + if (more) + { + more = visit_begin_array(shape.size(), tag, context, ec); + for (auto it = shape.begin(); more && it != shape.end(); ++it) + { + visit_uint64(*it, semantic_tag::none, context, ec); + } + if (more) + { + more = visit_end_array(context, ec); + } + } + return more; + } + + virtual bool visit_end_multi_dim(const ser_context& context, + std::error_code& ec) + { + return visit_end_array(context, ec); + } + }; + + template + class basic_default_json_visitor : public basic_json_visitor + { + bool parse_more_; + std::error_code ec_; + public: + using typename basic_json_visitor::string_view_type; + + basic_default_json_visitor(bool accept_more = true, + std::error_code ec = std::error_code()) + : parse_more_(accept_more), ec_(ec) + { + } + private: + void visit_flush() override + { + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_end_object(const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_end_array(const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_key(const string_view_type&, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_null(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_string(const string_view_type&, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_byte_string(const byte_string_view&, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_uint64(uint64_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_int64(int64_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_half(uint16_t, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_double(double, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + + bool visit_bool(bool, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (ec_) + { + ec = ec_; + } + return parse_more_; + } + }; + + template + class basic_json_diagnostics_visitor : public basic_default_json_visitor + { + public: + using stream_type = std::basic_ostream; + using string_type = std::basic_string; + + private: + using supertype = basic_default_json_visitor; + using string_view_type = typename supertype::string_view_type; + + struct enabler {}; + + static constexpr CharT visit_begin_array_name[] = {'v','i','s','i','t','_','b','e','g','i','n','_','a','r','r','a','y', 0}; + static constexpr CharT visit_end_array_name[] = {'v','i','s','i','t','_','e','n','d','_','a','r','r','a','y', 0}; + static constexpr CharT visit_begin_object_name[] = {'v','i','s','i','t','_','b','e','g','i','n','_','o','b','j','e','c','t', 0}; + static constexpr CharT visit_end_object_name[] = {'v','i','s','i','t','_','e','n','d','_','o','b','j','e','c','t', 0}; + static constexpr CharT visit_key_name[] = {'v','i','s','i','t','_','k','e','y', 0}; + static constexpr CharT visit_string_name[] = {'v','i','s','i','t','_','s','t','r','i','n','g', 0}; + static constexpr CharT visit_byte_string_name[] = {'v','i','s','i','t','_','b','y','t','e','_','s','t','r','i','n','g', 0}; + static constexpr CharT visit_null_name[] = {'v','i','s','i','t','_','n','u','l','l', 0}; + static constexpr CharT visit_bool_name[] = {'v','i','s','i','t','_','b','o','o','l', 0}; + static constexpr CharT visit_uint64_name[] = {'v','i','s','i','t','_','u','i','n','t','6','4', 0}; + static constexpr CharT visit_int64_name[] = {'v','i','s','i','t','_','i','n','t','6','4', 0}; + static constexpr CharT visit_half_name[] = {'v','i','s','i','t','_','h','a','l','f', 0}; + static constexpr CharT visit_double_name[] = {'v','i','s','i','t','_','d','o','u','b','l','e', 0}; + + static constexpr CharT separator_ = ':'; + + stream_type& output_; + string_type indentation_; + long level_; + + public: + // If CharT is char, then enable the default constructor which binds to + // std::cout. + template + basic_json_diagnostics_visitor( + typename std::enable_if::value, U>::type = enabler{}) + : basic_json_diagnostics_visitor(std::cout) + { + } + + // If CharT is wchar_t, then enable the default constructor which binds + // to std::wcout. + template + basic_json_diagnostics_visitor( + typename std::enable_if::value, U>::type = enabler{}) + : basic_json_diagnostics_visitor(std::wcout) + { + } + + explicit basic_json_diagnostics_visitor( + stream_type& output, + string_type indentation = string_type()) + : output_(output), + indentation_(std::move(indentation)), + level_(0) + { + } + + private: + void indent() + { + for (long i=0; i= 201703L +// not needed for C++17 +#else + template constexpr C basic_json_diagnostics_visitor::visit_begin_array_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_end_array_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_begin_object_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_end_object_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_key_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_string_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_byte_string_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_null_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_bool_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_uint64_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_int64_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_half_name[]; + template constexpr C basic_json_diagnostics_visitor::visit_double_name[]; +#endif // C++17 check + + using json_visitor = basic_json_visitor; + using wjson_visitor = basic_json_visitor; + + using default_json_visitor = basic_default_json_visitor; + using wdefault_json_visitor = basic_default_json_visitor; + + using json_diagnostics_visitor = basic_json_diagnostics_visitor; + using wjson_diagnostics_visitor = basic_json_diagnostics_visitor; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/pretty_print.hpp b/third_party/jsoncons/pretty_print.hpp new file mode 100644 index 0000000000..4bc07ea27e --- /dev/null +++ b/third_party/jsoncons/pretty_print.hpp @@ -0,0 +1,89 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_PRETTY_PRINT_HPP +#define JSONCONS_PRETTY_PRINT_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + +template +class json_printable +{ +public: + using char_type = typename Json::char_type; + + json_printable(const Json& j, indenting indent) + : j_(&j), indenting_(indent) + { + } + + json_printable(const Json& j, + const basic_json_encode_options& options, + indenting indent) + : j_(&j), options_(options), indenting_(indent) + { + } + + void dump(std::basic_ostream& os) const + { + j_->dump(os, options_, indenting_); + } + + friend std::basic_ostream& operator<<(std::basic_ostream& os, const json_printable& pr) + { + pr.dump(os); + return os; + } + + const Json *j_; + basic_json_encode_options options_; + indenting indenting_; +private: + json_printable(); +}; + +template +json_printable print(const Json& j) +{ + return json_printable(j, indenting::no_indent); +} + +template +json_printable print(const Json& j, + const basic_json_encode_options& options) +{ + return json_printable(j, options, indenting::no_indent); +} + +template +json_printable pretty_print(const Json& j) +{ + return json_printable(j, indenting::indent); +} + +template +json_printable pretty_print(const Json& j, + const basic_json_encode_options& options) +{ + return json_printable(j, options, indenting::indent); +} + +} + +#endif diff --git a/third_party/jsoncons/ser_context.hpp b/third_party/jsoncons/ser_context.hpp new file mode 100644 index 0000000000..d89cc93fec --- /dev/null +++ b/third_party/jsoncons/ser_context.hpp @@ -0,0 +1,39 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_SER_CONTEXT_HPP +#define JSONCONS_SER_CONTEXT_HPP + +namespace jsoncons { + +class ser_context +{ +public: + virtual ~ser_context() noexcept = default; + + virtual size_t line() const + { + return 0; + } + + virtual size_t column() const + { + return 0; + } + + virtual size_t position() const + { + return 0; + } + + virtual size_t end_position() const + { + return 0; + } +}; + +} +#endif diff --git a/third_party/jsoncons/sink.hpp b/third_party/jsoncons/sink.hpp new file mode 100644 index 0000000000..baefcc88fe --- /dev/null +++ b/third_party/jsoncons/sink.hpp @@ -0,0 +1,289 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_SINK_HPP +#define JSONCONS_SINK_HPP + +#include +#include +#include +#include +#include +#include +#include // std::addressof +#include // std::memcpy +#include +#include + +namespace jsoncons { + + // stream_sink + + template + class stream_sink + { + public: + using value_type = CharT; + using container_type = std::basic_ostream; + + private: + static constexpr size_t default_buffer_length = 16384; + + std::basic_ostream* stream_ptr_; + std::vector buffer_; + CharT * begin_buffer_; + const CharT* end_buffer_; + CharT* p_; + + // Noncopyable + stream_sink(const stream_sink&) = delete; + stream_sink& operator=(const stream_sink&) = delete; + + public: + stream_sink(stream_sink&&) = default; + + stream_sink(std::basic_ostream& os) + : stream_ptr_(std::addressof(os)), buffer_(default_buffer_length), begin_buffer_(buffer_.data()), end_buffer_(begin_buffer_+buffer_.size()), p_(begin_buffer_) + { + } + stream_sink(std::basic_ostream& os, std::size_t buflen) + : stream_ptr_(std::addressof(os)), buffer_(buflen), begin_buffer_(buffer_.data()), end_buffer_(begin_buffer_+buffer_.size()), p_(begin_buffer_) + { + } + ~stream_sink() noexcept + { + stream_ptr_->write(begin_buffer_, buffer_length()); + stream_ptr_->flush(); + } + + // Movable + stream_sink& operator=(stream_sink&&) = default; + + void flush() + { + stream_ptr_->write(begin_buffer_, buffer_length()); + stream_ptr_->flush(); + p_ = buffer_.data(); + } + + void append(const CharT* s, std::size_t length) + { + std::size_t diff = end_buffer_ - p_; + if (diff >= length) + { + std::memcpy(p_, s, length*sizeof(CharT)); + p_ += length; + } + else + { + stream_ptr_->write(begin_buffer_, buffer_length()); + stream_ptr_->write(s,length); + p_ = begin_buffer_; + } + } + + void push_back(CharT ch) + { + if (p_ < end_buffer_) + { + *p_++ = ch; + } + else + { + stream_ptr_->write(begin_buffer_, buffer_length()); + p_ = begin_buffer_; + push_back(ch); + } + } + private: + + std::size_t buffer_length() const + { + return p_ - begin_buffer_; + } + }; + + // binary_stream_sink + + class binary_stream_sink + { + public: + typedef uint8_t value_type; + using container_type = std::basic_ostream; + private: + static constexpr size_t default_buffer_length = 16384; + + std::basic_ostream* stream_ptr_; + std::vector buffer_; + uint8_t * begin_buffer_; + const uint8_t* end_buffer_; + uint8_t* p_; + + // Noncopyable + binary_stream_sink(const binary_stream_sink&) = delete; + binary_stream_sink& operator=(const binary_stream_sink&) = delete; + + public: + binary_stream_sink(binary_stream_sink&&) = default; + + binary_stream_sink(std::basic_ostream& os) + : stream_ptr_(std::addressof(os)), + buffer_(default_buffer_length), + begin_buffer_(buffer_.data()), + end_buffer_(begin_buffer_+buffer_.size()), + p_(begin_buffer_) + { + } + binary_stream_sink(std::basic_ostream& os, std::size_t buflen) + : stream_ptr_(std::addressof(os)), + buffer_(buflen), + begin_buffer_(buffer_.data()), + end_buffer_(begin_buffer_+buffer_.size()), + p_(begin_buffer_) + { + } + ~binary_stream_sink() noexcept + { + stream_ptr_->write((char*)begin_buffer_, buffer_length()); + stream_ptr_->flush(); + } + + binary_stream_sink& operator=(binary_stream_sink&&) = default; + + void flush() + { + stream_ptr_->write((char*)begin_buffer_, buffer_length()); + p_ = buffer_.data(); + } + + void append(const uint8_t* s, std::size_t length) + { + std::size_t diff = end_buffer_ - p_; + if (diff >= length) + { + std::memcpy(p_, s, length*sizeof(uint8_t)); + p_ += length; + } + else + { + stream_ptr_->write((char*)begin_buffer_, buffer_length()); + stream_ptr_->write((const char*)s,length); + p_ = begin_buffer_; + } + } + + void push_back(uint8_t ch) + { + if (p_ < end_buffer_) + { + *p_++ = ch; + } + else + { + stream_ptr_->write((char*)begin_buffer_, buffer_length()); + p_ = begin_buffer_; + push_back(ch); + } + } + private: + + std::size_t buffer_length() const + { + return p_ - begin_buffer_; + } + }; + + // string_sink + + template + class string_sink + { + public: + using value_type = typename StringT::value_type; + using container_type = StringT; + private: + container_type* buf_ptr; + + // Noncopyable + string_sink(const string_sink&) = delete; + string_sink& operator=(const string_sink&) = delete; + public: + string_sink(string_sink&& other) noexcept + : buf_ptr(nullptr) + { + std::swap(buf_ptr,other.buf_ptr); + } + + string_sink(container_type& buf) + : buf_ptr(std::addressof(buf)) + { + } + + string_sink& operator=(string_sink&& other) noexcept + { + // TODO: Shouldn't other.buf_ptr be nullified? + // Also see move constructor above. + std::swap(buf_ptr,other.buf_ptr); + return *this; + } + + void flush() + { + } + + void append(const value_type* s, std::size_t length) + { + buf_ptr->insert(buf_ptr->end(), s, s+length); + } + + void push_back(value_type ch) + { + buf_ptr->push_back(ch); + } + }; + + // bytes_sink + + template + class bytes_sink + { + }; + + template + class bytes_sink::value>::type> + { + public: + using container_type = Container; + using value_type = typename Container::value_type; + private: + container_type* buf_ptr; + + // Noncopyable + bytes_sink(const bytes_sink&) = delete; + bytes_sink& operator=(const bytes_sink&) = delete; + public: + bytes_sink(bytes_sink&&) = default; + + bytes_sink(container_type& buf) + : buf_ptr(std::addressof(buf)) + { + } + + bytes_sink& operator=(bytes_sink&&) = default; + + void flush() + { + } + + void push_back(uint8_t ch) + { + buf_ptr->push_back(static_cast(ch)); + } + }; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/source.hpp b/third_party/jsoncons/source.hpp new file mode 100644 index 0000000000..f679bd4c12 --- /dev/null +++ b/third_party/jsoncons/source.hpp @@ -0,0 +1,793 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_SOURCE_HPP +#define JSONCONS_SOURCE_HPP + +#include +#include +#include +#include +#include // std::addressof +#include // std::memcpy +#include +#include +#include // std::enable_if +#include +#include // jsoncons::byte_traits +#include + +namespace jsoncons { + + template + class basic_null_istream : public std::basic_istream + { + class null_buffer : public std::basic_streambuf + { + null_buffer(const null_buffer&) = delete; + null_buffer& operator=(const null_buffer&) = delete; + public: + using typename std::basic_streambuf::int_type; + using typename std::basic_streambuf::traits_type; + + null_buffer() = default; + null_buffer(null_buffer&&) = default; + null_buffer& operator=(null_buffer&&) = default; + + int_type overflow( int_type ch = typename std::basic_streambuf::traits_type::eof() ) override + { + return ch; + } + } nb_; + public: + basic_null_istream() + : std::basic_istream(&nb_) + { + } + + basic_null_istream(const null_buffer&) = delete; + basic_null_istream& operator=(const null_buffer&) = delete; + basic_null_istream(basic_null_istream&&) noexcept + : std::basic_istream(&nb_) + { + } + basic_null_istream& operator=(basic_null_istream&&) noexcept + { + return *this; + } + }; + + template + struct char_result + { + CharT value; + bool eof; + }; + + // text sources + + template + class stream_source + { + static constexpr std::size_t default_max_buffer_size = 16384; + public: + using value_type = CharT; + private: + using char_type = typename std::conditional::type; + basic_null_istream null_is_; + std::basic_istream* stream_ptr_; + std::basic_streambuf* sbuf_; + std::size_t position_; + std::vector buffer_; + const value_type* buffer_data_; + std::size_t buffer_length_; + + // Noncopyable + stream_source(const stream_source&) = delete; + stream_source& operator=(const stream_source&) = delete; + public: + stream_source() + : stream_ptr_(&null_is_), sbuf_(null_is_.rdbuf()), position_(0), + buffer_(1), buffer_data_(buffer_.data()), buffer_length_(0) + { + } + + stream_source(std::basic_istream& is, std::size_t buf_size = default_max_buffer_size) + : stream_ptr_(std::addressof(is)), sbuf_(is.rdbuf()), position_(0), + buffer_(buf_size), buffer_data_(buffer_.data()), buffer_length_(0) + { + } + + stream_source(stream_source&& other) noexcept + : stream_ptr_(&null_is_), sbuf_(null_is_.rdbuf()), position_(0), + buffer_(), buffer_data_(buffer_.data()), buffer_length_(0) + { + if (other.stream_ptr_ != &other.null_is_) + { + stream_ptr_ = other.stream_ptr_; + sbuf_ = other.sbuf_; + position_ = other.position_; + buffer_ = std::move(other.buffer_); + buffer_data_ = buffer_.data() + (other.buffer_data_ - other.buffer_.data()); + buffer_length_ = other.buffer_length_; + other = stream_source(); + } + } + + ~stream_source() + { + } + + stream_source& operator=(stream_source&& other) noexcept + { + if (other.stream_ptr_ != &other.null_is_) + { + stream_ptr_ = other.stream_ptr_; + sbuf_ = other.sbuf_; + position_ = other.position_; + buffer_ = std::move(other.buffer_); + buffer_data_ = buffer_.data() + (other.buffer_data_ - other.buffer_.data()); + buffer_length_ = other.buffer_length_; + other = stream_source(); + } + else + { + stream_ptr_ = &null_is_; + sbuf_ = null_is_.rdbuf(); + position_ = 0; + buffer_data_ = buffer_.data(); + buffer_length_ = 0; + } + return *this; + } + + bool eof() const + { + return buffer_length_ == 0 && stream_ptr_->eof(); + } + + bool is_error() const + { + return stream_ptr_->bad(); + } + + std::size_t position() const + { + return position_; + } + + void ignore(std::size_t length) + { + std::size_t len = 0; + if (buffer_length_ > 0) + { + len = (std::min)(buffer_length_, length); + position_ += len; + buffer_data_ += len; + buffer_length_ -= len; + } + while (len < length) + { + fill_buffer(); + if (buffer_length_ == 0) + { + break; + } + std::size_t len2 = (std::min)(buffer_length_, length-len); + position_ += len2; + buffer_data_ += len2; + buffer_length_ -= len2; + len += len2; + } + } + + char_result peek() + { + if (buffer_length_ == 0) + { + fill_buffer(); + } + if (buffer_length_ > 0) + { + value_type c = *buffer_data_; + return char_result{c, false}; + } + else + { + return char_result{0, true}; + } + } + + span read_buffer() + { + if (buffer_length_ == 0) + { + fill_buffer(); + } + const value_type* data = buffer_data_; + std::size_t length = buffer_length_; + buffer_data_ += buffer_length_; + position_ += buffer_length_; + buffer_length_ = 0; + + return span(data, length); + } + + std::size_t read(value_type* p, std::size_t length) + { + std::size_t len = 0; + if (buffer_length_ > 0) + { + len = (std::min)(buffer_length_, length); + std::memcpy(p, buffer_data_, len*sizeof(value_type)); + buffer_data_ += len; + buffer_length_ -= len; + position_ += len; + } + if (length - len == 0) + { + return len; + } + else if (length - len < buffer_.size()) + { + fill_buffer(); + if (buffer_length_ > 0) + { + std::size_t len2 = (std::min)(buffer_length_, length-len); + std::memcpy(p+len, buffer_data_, len2*sizeof(value_type)); + buffer_data_ += len2; + buffer_length_ -= len2; + position_ += len2; + len += len2; + } + return len; + } + else + { + if (stream_ptr_->eof()) + { + buffer_length_ = 0; + return 0; + } + JSONCONS_TRY + { + std::streamsize count = sbuf_->sgetn(reinterpret_cast(p+len), length-len); + std::size_t len2 = static_cast(count); + if (len2 < length-len) + { + stream_ptr_->clear(stream_ptr_->rdstate() | std::ios::eofbit); + } + len += len2; + position_ += len2; + return len; + } + JSONCONS_CATCH(const std::exception&) + { + stream_ptr_->clear(stream_ptr_->rdstate() | std::ios::badbit | std::ios::eofbit); + return 0; + } + } + } + private: + void fill_buffer() + { + if (stream_ptr_->eof()) + { + buffer_length_ = 0; + return; + } + + buffer_data_ = buffer_.data(); + JSONCONS_TRY + { + std::streamsize count = sbuf_->sgetn(reinterpret_cast(buffer_.data()), buffer_.size()); + buffer_length_ = static_cast(count); + + if (buffer_length_ < buffer_.size()) + { + stream_ptr_->clear(stream_ptr_->rdstate() | std::ios::eofbit); + } + } + JSONCONS_CATCH(const std::exception&) + { + stream_ptr_->clear(stream_ptr_->rdstate() | std::ios::badbit | std::ios::eofbit); + buffer_length_ = 0; + } + } + }; + + // string_source + + template + class string_source + { + public: + using value_type = CharT; + using string_view_type = jsoncons::basic_string_view; + private: + const value_type* data_; + const value_type* current_; + const value_type* end_; + + // Noncopyable + string_source(const string_source&) = delete; + string_source& operator=(const string_source&) = delete; + public: + string_source() + : data_(nullptr), current_(nullptr), end_(nullptr) + { + } + + template + string_source(const Sourceable& s, + typename std::enable_if::value>::type* = 0) + : data_(s.data()), current_(s.data()), end_(s.data()+s.size()) + { + } + + string_source(const value_type* data) + : data_(data), current_(data), end_(data+std::char_traits::length(data)) + { + } + + string_source(string_source&& other) = default; + + string_source& operator=(string_source&& other) = default; + + bool eof() const + { + return current_ == end_; + } + + bool is_error() const + { + return false; + } + + std::size_t position() const + { + return (current_ - data_)/sizeof(value_type); + } + + void ignore(std::size_t count) + { + std::size_t len; + if (std::size_t(end_ - current_) < count) + { + len = end_ - current_; + } + else + { + len = count; + } + current_ += len; + } + + char_result peek() + { + return current_ < end_ ? char_result{*current_, false} : char_result{0, true}; + } + + span read_buffer() + { + const value_type* data = current_; + std::size_t length = end_ - current_; + current_ = end_; + + return span(data, length); + } + + std::size_t read(value_type* p, std::size_t length) + { + std::size_t len; + if (std::size_t(end_ - current_) < length) + { + len = end_ - current_; + } + else + { + len = length; + } + std::memcpy(p, current_, len*sizeof(value_type)); + current_ += len; + return len; + } + }; + + // iterator source + + template + class iterator_source + { + public: + using value_type = typename std::iterator_traits::value_type; + private: + static constexpr std::size_t default_max_buffer_size = 16384; + + IteratorT current_; + IteratorT end_; + std::size_t position_; + std::vector buffer_; + std::size_t buffer_length_; + + using difference_type = typename std::iterator_traits::difference_type; + using iterator_category = typename std::iterator_traits::iterator_category; + + // Noncopyable + iterator_source(const iterator_source&) = delete; + iterator_source& operator=(const iterator_source&) = delete; + public: + + iterator_source(const IteratorT& first, const IteratorT& last, std::size_t buf_size = default_max_buffer_size) + : current_(first), end_(last), position_(0), buffer_(buf_size), buffer_length_(0) + { + } + + iterator_source(iterator_source&& other) = default; + + iterator_source& operator=(iterator_source&& other) = default; + + bool eof() const + { + return !(current_ != end_); + } + + bool is_error() const + { + return false; + } + + std::size_t position() const + { + return position_; + } + + void ignore(std::size_t count) + { + while (count-- > 0 && current_ != end_) + { + ++position_; + ++current_; + } + } + + char_result peek() + { + return current_ != end_ ? char_result{*current_, false} : char_result{0, true}; + } + + span read_buffer() + { + if (buffer_length_ == 0) + { + buffer_length_ = read(buffer_.data(), buffer_.size()); + } + std::size_t length = buffer_length_; + buffer_length_ = 0; + + return span(buffer_.data(), length); + } + + template + typename std::enable_if::value, std::size_t>::type + read(value_type* data, std::size_t length) + { + std::size_t count = (std::min)(length, static_cast(std::distance(current_, end_))); + + //JSONCONS_COPY(current_, current_ + count, data); + + auto end = current_ + count; + value_type* p = data; + while (current_ != end) + { + *p++ = *current_++; + } + + //current_ += count; + position_ += count; + + return count; + } + + template + typename std::enable_if::value, std::size_t>::type + read(value_type* data, std::size_t length) + { + value_type* p = data; + value_type* pend = data + length; + + while (p < pend && current_ != end_) + { + *p = static_cast(*current_); + ++p; + ++current_; + } + + position_ += (p - data); + + return p - data; + } + }; + + // binary sources + + using binary_stream_source = stream_source; + + class bytes_source + { + public: + typedef uint8_t value_type; + private: + const value_type* data_; + const value_type* current_; + const value_type* end_; + + // Noncopyable + bytes_source(const bytes_source&) = delete; + bytes_source& operator=(const bytes_source&) = delete; + public: + bytes_source() + : data_(nullptr), current_(nullptr), end_(nullptr) + { + } + + template + bytes_source(const Sourceable& source, + typename std::enable_if::value,int>::type = 0) + : data_(reinterpret_cast(source.data())), + current_(data_), + end_(data_+source.size()) + { + } + + bytes_source(bytes_source&&) = default; + + bytes_source& operator=(bytes_source&&) = default; + + bool eof() const + { + return current_ == end_; + } + + bool is_error() const + { + return false; + } + + std::size_t position() const + { + return current_ - data_; + } + + void ignore(std::size_t count) + { + std::size_t len; + if (std::size_t(end_ - current_) < count) + { + len = end_ - current_; + } + else + { + len = count; + } + current_ += len; + } + + char_result peek() + { + return current_ < end_ ? char_result{*current_, false} : char_result{0, true}; + } + + span read_buffer() + { + const value_type* data = current_; + std::size_t length = end_ - current_; + current_ = end_; + + return span(data, length); + } + + std::size_t read(value_type* p, std::size_t length) + { + std::size_t len; + if (std::size_t(end_ - current_) < length) + { + len = end_ - current_; + } + else + { + len = length; + } + std::memcpy(p, current_, len*sizeof(value_type)); + current_ += len; + return len; + } + }; + + // binary_iterator source + + template + class binary_iterator_source + { + public: + using value_type = uint8_t; + private: + static constexpr std::size_t default_max_buffer_size = 16384; + + IteratorT current_; + IteratorT end_; + std::size_t position_; + std::vector buffer_; + std::size_t buffer_length_; + + using difference_type = typename std::iterator_traits::difference_type; + using iterator_category = typename std::iterator_traits::iterator_category; + + // Noncopyable + binary_iterator_source(const binary_iterator_source&) = delete; + binary_iterator_source& operator=(const binary_iterator_source&) = delete; + public: + binary_iterator_source(const IteratorT& first, const IteratorT& last, std::size_t buf_size = default_max_buffer_size) + : current_(first), end_(last), position_(0), buffer_(buf_size), buffer_length_(0) + { + } + + binary_iterator_source(binary_iterator_source&& other) = default; + + binary_iterator_source& operator=(binary_iterator_source&& other) = default; + + bool eof() const + { + return !(current_ != end_); + } + + bool is_error() const + { + return false; + } + + std::size_t position() const + { + return position_; + } + + void ignore(std::size_t count) + { + while (count-- > 0 && current_ != end_) + { + ++position_; + ++current_; + } + } + + char_result peek() + { + return current_ != end_ ? char_result{static_cast(*current_), false} : char_result{0, true}; + } + + span read_buffer() + { + if (buffer_length_ == 0) + { + buffer_length_ = read(buffer_.data(), buffer_.size()); + } + std::size_t length = buffer_length_; + buffer_length_ = 0; + + return span(buffer_.data(), length); + } + + template + typename std::enable_if::value, std::size_t>::type + read(value_type* data, std::size_t length) + { + std::size_t count = (std::min)(length, static_cast(std::distance(current_, end_))); + //JSONCONS_COPY(current_, current_ + count, data); + + auto end = current_ + count; + value_type* p = data; + while (current_ != end) + { + *p++ = *current_++; + } + + //current_ += count; + position_ += count; + + return count; + } + + template + typename std::enable_if::value, std::size_t>::type + read(value_type* data, std::size_t length) + { + value_type* p = data; + value_type* pend = data + length; + + while (p < pend && current_ != end_) + { + *p = static_cast(*current_); + ++p; + ++current_; + } + + position_ += (p - data); + + return p - data; + } + }; + + template + struct source_reader + { + using value_type = typename Source::value_type; + static constexpr std::size_t max_buffer_length = 16384; + + template + static + typename std::enable_if::value && + extension_traits::has_reserve::value && + extension_traits::has_data_exact::value + , std::size_t>::type + read(Source& source, Container& v, std::size_t length) + { + std::size_t unread = length; + + std::size_t n = (std::min)(max_buffer_length, unread); + while (n > 0 && !source.eof()) + { + std::size_t offset = v.size(); + v.resize(v.size()+n); + std::size_t actual = source.read(v.data()+offset, n); + unread -= actual; + n = (std::min)(max_buffer_length, unread); + } + + return length - unread; + } + + template + static + typename std::enable_if::value && + extension_traits::has_reserve::value && + !extension_traits::has_data_exact::value + , std::size_t>::type + read(Source& source, Container& v, std::size_t length) + { + std::size_t unread = length; + + std::size_t n = (std::min)(max_buffer_length, unread); + while (n > 0 && !source.eof()) + { + v.reserve(v.size()+n); + std::size_t actual = 0; + while (actual < n) + { + typename Source::value_type c; + if (source.read(&c,1) != 1) + { + break; + } + v.push_back(c); + ++actual; + } + unread -= actual; + n = (std::min)(max_buffer_length, unread); + } + + return length - unread; + } + }; +#if __cplusplus >= 201703L +// not needed for C++17 +#else + template + constexpr std::size_t source_reader::max_buffer_length; +#endif + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/source_adaptor.hpp b/third_party/jsoncons/source_adaptor.hpp new file mode 100644 index 0000000000..320ddcfcb5 --- /dev/null +++ b/third_party/jsoncons/source_adaptor.hpp @@ -0,0 +1,148 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BUFFER_READER_HPP +#define JSONCONS_BUFFER_READER_HPP + +#include +#include +#include +#include +#include +#include // std::allocator_traits +#include // std::vector +#include +#include // json_errc +#include +#include + +namespace jsoncons { + + // text_source_adaptor + + template + class text_source_adaptor + { + public: + using value_type = typename Source::value_type; + private: + Source source_; + bool bof_; + + public: + text_source_adaptor() + : bof_(true) + { + } + + template + text_source_adaptor(Sourceable&& source) + : source_(std::forward(source)), bof_(true) + { + } + + bool eof() const + { + return source_.eof(); + } + + bool is_error() const + { + return source_.is_error(); + } + + span read_buffer(std::error_code& ec) + { + if (source_.eof()) + { + return span(); + } + + auto s = source_.read_buffer(); + const value_type* data = s.data(); + std::size_t length = s.size(); + + if (bof_ && length > 0) + { + auto r = unicode_traits::detect_encoding_from_bom(data, length); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + ec = json_errc::illegal_unicode_character; + return span(); + } + length -= (r.ptr - data); + data = r.ptr; + bof_ = false; + } + return span(data, length); + } + }; + + // json_source_adaptor + + template + class json_source_adaptor + { + public: + using value_type = typename Source::value_type; + private: + Source source_; + bool bof_; + + public: + json_source_adaptor() + : bof_(true) + { + } + + template + json_source_adaptor(Sourceable&& source) + : source_(std::forward(source)), bof_(true) + { + } + + bool eof() const + { + return source_.eof(); + } + + bool is_error() const + { + return source_.is_error(); + } + + span read_buffer(std::error_code& ec) + { + if (source_.eof()) + { + return span(); + } + + auto s = source_.read_buffer(); + const value_type* data = s.data(); + std::size_t length = s.size(); + + if (bof_ && length > 0) + { + auto r = unicode_traits::detect_json_encoding(data, length); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + ec = json_errc::illegal_unicode_character; + return span(); + } + length -= (r.ptr - data); + data = r.ptr; + bof_ = false; + } + + return span(data, length); + } + }; + +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons/staj_cursor.hpp b/third_party/jsoncons/staj_cursor.hpp new file mode 100644 index 0000000000..b26ce180a9 --- /dev/null +++ b/third_party/jsoncons/staj_cursor.hpp @@ -0,0 +1,746 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_STAJ_CURSOR_HPP +#define JSONCONS_STAJ_CURSOR_HPP + +#include // std::allocator +#include +#include +#include +#include +#include // std::enable_if +#include // std::array +#include // std::function +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + +// basic_staj_visitor + +enum class staj_cursor_state +{ + typed_array = 1, + multi_dim, + shape +}; + +template +class basic_staj_visitor : public basic_json_visitor +{ + using super_type = basic_json_visitor; +public: + using char_type = CharT; + using typename super_type::string_view_type; +private: + std::function&, const ser_context&)> pred_; + basic_staj_event event_; + + staj_cursor_state state_; + typed_array_view data_; + jsoncons::span shape_; + std::size_t index_; +public: + basic_staj_visitor() + : pred_(accept), event_(staj_event_type::null_value), + state_(), data_(), shape_(), index_(0) + { + } + + basic_staj_visitor(std::function&, const ser_context&)> pred) + : pred_(pred), event_(staj_event_type::null_value), + state_(), data_(), shape_(), index_(0) + { + } + + void reset() + { + event_ = staj_event_type::null_value; + state_ = {}; + data_ = {}; + shape_ = {}; + index_ = 0; + } + + const basic_staj_event& event() const + { + return event_; + } + + bool in_available() const + { + return state_ != staj_cursor_state(); + } + + void send_available(std::error_code& ec) + { + switch (state_) + { + case staj_cursor_state::typed_array: + advance_typed_array(ec); + break; + case staj_cursor_state::multi_dim: + case staj_cursor_state::shape: + advance_multi_dim(ec); + break; + default: + break; + } + } + + bool is_typed_array() const + { + return data_.type() != typed_array_type(); + } + + staj_cursor_state state() const + { + return state_; + } + + void advance_typed_array(std::error_code& ec) + { + if (is_typed_array()) + { + if (index_ < data_.size()) + { + switch (data_.type()) + { + case typed_array_type::uint8_value: + { + this->uint64_value(data_.data(uint8_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::uint16_value: + { + this->uint64_value(data_.data(uint16_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::uint32_value: + { + this->uint64_value(data_.data(uint32_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::uint64_value: + { + this->uint64_value(data_.data(uint64_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int8_value: + { + this->int64_value(data_.data(int8_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int16_value: + { + this->int64_value(data_.data(int16_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int32_value: + { + this->int64_value(data_.data(int32_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int64_value: + { + this->int64_value(data_.data(int64_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::half_value: + { + this->half_value(data_.data(half_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::float_value: + { + this->double_value(data_.data(float_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::double_value: + { + this->double_value(data_.data(double_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + default: + break; + } + ++index_; + } + else + { + this->end_array(); + state_ = staj_cursor_state(); + data_ = typed_array_view(); + index_ = 0; + } + } + } + + void advance_multi_dim(std::error_code& ec) + { + if (shape_.size() != 0) + { + if (state_ == staj_cursor_state::multi_dim) + { + this->begin_array(shape_.size(), semantic_tag::none, ser_context(), ec); + state_ = staj_cursor_state::shape; + } + else if (index_ < shape_.size()) + { + this->uint64_value(shape_[index_], semantic_tag::none, ser_context(), ec); + ++index_; + } + else + { + state_ = staj_cursor_state(); + this->end_array(ser_context(), ec); + shape_ = jsoncons::span(); + index_ = 0; + } + } + } + + bool dump(basic_json_visitor& visitor, const ser_context& context, std::error_code& ec) + { + bool more = true; + if (is_typed_array()) + { + if (index_ != 0) + { + more = event().send_json_event(visitor, context, ec); + while (more && is_typed_array()) + { + if (index_ < data_.size()) + { + switch (data_.type()) + { + case typed_array_type::uint8_value: + { + more = visitor.uint64_value(data_.data(uint8_array_arg)[index_]); + break; + } + case typed_array_type::uint16_value: + { + more = visitor.uint64_value(data_.data(uint16_array_arg)[index_]); + break; + } + case typed_array_type::uint32_value: + { + more = visitor.uint64_value(data_.data(uint32_array_arg)[index_]); + break; + } + case typed_array_type::uint64_value: + { + more = visitor.uint64_value(data_.data(uint64_array_arg)[index_]); + break; + } + case typed_array_type::int8_value: + { + more = visitor.int64_value(data_.data(int8_array_arg)[index_]); + break; + } + case typed_array_type::int16_value: + { + more = visitor.int64_value(data_.data(int16_array_arg)[index_]); + break; + } + case typed_array_type::int32_value: + { + more = visitor.int64_value(data_.data(int32_array_arg)[index_]); + break; + } + case typed_array_type::int64_value: + { + more = visitor.int64_value(data_.data(int64_array_arg)[index_]); + break; + } + case typed_array_type::float_value: + { + more = visitor.double_value(data_.data(float_array_arg)[index_]); + break; + } + case typed_array_type::double_value: + { + more = visitor.double_value(data_.data(double_array_arg)[index_]); + break; + } + default: + break; + } + ++index_; + } + else + { + more = visitor.end_array(); + state_ = staj_cursor_state(); + data_ = typed_array_view(); + index_ = 0; + } + } + } + else + { + switch (data_.type()) + { + case typed_array_type::uint8_value: + { + more = visitor.typed_array(data_.data(uint8_array_arg)); + break; + } + case typed_array_type::uint16_value: + { + more = visitor.typed_array(data_.data(uint16_array_arg)); + break; + } + case typed_array_type::uint32_value: + { + more = visitor.typed_array(data_.data(uint32_array_arg)); + break; + } + case typed_array_type::uint64_value: + { + more = visitor.typed_array(data_.data(uint64_array_arg)); + break; + } + case typed_array_type::int8_value: + { + more = visitor.typed_array(data_.data(int8_array_arg)); + break; + } + case typed_array_type::int16_value: + { + more = visitor.typed_array(data_.data(int16_array_arg)); + break; + } + case typed_array_type::int32_value: + { + more = visitor.typed_array(data_.data(int32_array_arg)); + break; + } + case typed_array_type::int64_value: + { + more = visitor.typed_array(data_.data(int64_array_arg)); + break; + } + case typed_array_type::float_value: + { + more = visitor.typed_array(data_.data(float_array_arg)); + break; + } + case typed_array_type::double_value: + { + more = visitor.typed_array(data_.data(double_array_arg)); + break; + } + default: + break; + } + + state_ = staj_cursor_state(); + data_ = typed_array_view(); + } + } + else + { + more = event().send_json_event(visitor, context, ec); + } + return more; + } + +private: + static constexpr bool accept(const basic_staj_event&, const ser_context&) + { + return true; + } + + bool visit_begin_object(semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_object, tag); + return !pred_(event_, context); + } + + bool visit_begin_object(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_object, length, tag); + return !pred_(event_, context); + } + + bool visit_end_object(const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::end_object); + return !pred_(event_, context); + } + + bool visit_begin_array(semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_array, tag); + return !pred_(event_, context); + } + + bool visit_begin_array(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_array, length, tag); + return !pred_(event_, context); + } + + bool visit_end_array(const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::end_array); + return !pred_(event_, context); + } + + bool visit_key(const string_view_type& name, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(name, staj_event_type::key); + return !pred_(event_, context); + } + + bool visit_null(semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::null_value, tag); + return !pred_(event_, context); + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_string(const string_view_type& s, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(s, staj_event_type::string_value, tag); + return !pred_(event_, context); + } + + bool visit_byte_string(const byte_string_view& s, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(s, staj_event_type::byte_string_value, tag); + return !pred_(event_, context); + } + + bool visit_byte_string(const byte_string_view& s, + uint64_t ext_tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(s, staj_event_type::byte_string_value, ext_tag); + return !pred_(event_, context); + } + + bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_half(uint16_t value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(half_arg, value, tag); + return !pred_(event_, context); + } + + bool visit_double(double value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_typed_array(const jsoncons::span& v, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(v.data(), v.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(half_arg_t, const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } +/* + bool visit_typed_array(const jsoncons::span&, + semantic_tag, + const ser_context&, + std::error_code&) override + { + return true; + } +*/ + bool visit_begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = staj_cursor_state::multi_dim; + shape_ = shape; + return this->begin_array(2, tag, context, ec); + } + + bool visit_end_multi_dim(const ser_context& context, + std::error_code& ec) override + { + return this->end_array(context, ec); + } + + void visit_flush() override + { + } +}; + + +// basic_staj_cursor + +template +class basic_staj_cursor +{ +public: + virtual ~basic_staj_cursor() noexcept = default; + + virtual void array_expected(std::error_code& ec) + { + if (!(current().event_type() == staj_event_type::begin_array || current().event_type() == staj_event_type::byte_string_value)) + { + ec = conv_errc::not_vector; + } + } + + virtual bool done() const = 0; + + virtual const basic_staj_event& current() const = 0; + + virtual void read_to(basic_json_visitor& visitor) = 0; + + virtual void read_to(basic_json_visitor& visitor, + std::error_code& ec) = 0; + + virtual void next() = 0; + + virtual void next(std::error_code& ec) = 0; + + virtual const ser_context& context() const = 0; +}; + +template +class basic_staj_filter_view : basic_staj_cursor +{ + basic_staj_cursor* cursor_; + std::function&, const ser_context&)> pred_; +public: + basic_staj_filter_view(basic_staj_cursor& cursor, + std::function&, const ser_context&)> pred) + : cursor_(std::addressof(cursor)), pred_(pred) + { + while (!done() && !pred_(current(),context())) + { + cursor_->next(); + } + } + + bool done() const override + { + return cursor_->done(); + } + + const basic_staj_event& current() const override + { + return cursor_->current(); + } + + void read_to(basic_json_visitor& visitor) override + { + cursor_->read_to(visitor); + } + + void read_to(basic_json_visitor& visitor, + std::error_code& ec) override + { + cursor_->read_to(visitor, ec); + } + + void next() override + { + cursor_->next(); + while (!done() && !pred_(current(),context())) + { + cursor_->next(); + } + } + + void next(std::error_code& ec) override + { + cursor_->next(ec); + while (!done() && !pred_(current(),context()) && !ec) + { + cursor_->next(ec); + } + } + + const ser_context& context() const override + { + return cursor_->context(); + } + + friend + basic_staj_filter_view operator|(basic_staj_filter_view& cursor, + std::function&, const ser_context&)> pred) + { + return basic_staj_filter_view(cursor, pred); + } +}; + +using staj_event = basic_staj_event; +using wstaj_event = basic_staj_event; + +using staj_cursor = basic_staj_cursor; +using wstaj_cursor = basic_staj_cursor; + +using staj_filter_view = basic_staj_filter_view; +using wstaj_filter_view = basic_staj_filter_view; + +} + +#endif + diff --git a/third_party/jsoncons/staj_event.hpp b/third_party/jsoncons/staj_event.hpp new file mode 100644 index 0000000000..90bff40615 --- /dev/null +++ b/third_party/jsoncons/staj_event.hpp @@ -0,0 +1,541 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_STAJ_EVENT_HPP +#define JSONCONS_STAJ_EVENT_HPP + +#include // std::allocator +#include +#include +#include +#include +#include // std::enable_if +#include // std::array +#include // std::function +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + +enum class staj_event_type +{ + begin_array, + end_array, + begin_object, + end_object, + key, + string_value, + byte_string_value, + null_value, + bool_value, + int64_value, + uint64_value, + half_value, + double_value +}; + +template +std::basic_ostream& operator<<(std::basic_ostream& os, staj_event_type tag) +{ + static constexpr const CharT* begin_array_name = JSONCONS_CSTRING_CONSTANT(CharT, "begin_array"); + static constexpr const CharT* end_array_name = JSONCONS_CSTRING_CONSTANT(CharT, "end_array"); + static constexpr const CharT* begin_object_name = JSONCONS_CSTRING_CONSTANT(CharT, "begin_object"); + static constexpr const CharT* end_object_name = JSONCONS_CSTRING_CONSTANT(CharT, "end_object"); + static constexpr const CharT* key_name = JSONCONS_CSTRING_CONSTANT(CharT, "key"); + static constexpr const CharT* string_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "string_value"); + static constexpr const CharT* byte_string_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "byte_string_value"); + static constexpr const CharT* null_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "null_value"); + static constexpr const CharT* bool_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "bool_value"); + static constexpr const CharT* uint64_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "uint64_value"); + static constexpr const CharT* int64_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "int64_value"); + static constexpr const CharT* half_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "half_value"); + static constexpr const CharT* double_value_name = JSONCONS_CSTRING_CONSTANT(CharT, "double_value"); + + switch (tag) + { + case staj_event_type::begin_array: + { + os << begin_array_name; + break; + } + case staj_event_type::end_array: + { + os << end_array_name; + break; + } + case staj_event_type::begin_object: + { + os << begin_object_name; + break; + } + case staj_event_type::end_object: + { + os << end_object_name; + break; + } + case staj_event_type::key: + { + os << key_name; + break; + } + case staj_event_type::string_value: + { + os << string_value_name; + break; + } + case staj_event_type::byte_string_value: + { + os << byte_string_value_name; + break; + } + case staj_event_type::null_value: + { + os << null_value_name; + break; + } + case staj_event_type::bool_value: + { + os << bool_value_name; + break; + } + case staj_event_type::int64_value: + { + os << int64_value_name; + break; + } + case staj_event_type::uint64_value: + { + os << uint64_value_name; + break; + } + case staj_event_type::half_value: + { + os << half_value_name; + break; + } + case staj_event_type::double_value: + { + os << double_value_name; + break; + } + } + return os; +} + +template +class basic_staj_event +{ + staj_event_type event_type_; + semantic_tag tag_; + uint64_t ext_tag_; + union + { + bool bool_value_; + int64_t int64_value_; + uint64_t uint64_value_; + uint16_t half_value_; + double double_value_; + const CharT* string_data_; + const uint8_t* byte_string_data_; + } value_; + std::size_t length_; +public: + using string_view_type = jsoncons::basic_string_view; + + basic_staj_event(staj_event_type event_type, semantic_tag tag = semantic_tag::none) + : event_type_(event_type), tag_(tag), ext_tag_(0), value_(), length_(0) + { + } + + basic_staj_event(staj_event_type event_type, std::size_t length, semantic_tag tag = semantic_tag::none) + : event_type_(event_type), tag_(tag), ext_tag_(0), value_(), length_(length) + { + } + + basic_staj_event(null_type, semantic_tag tag) + : event_type_(staj_event_type::null_value), tag_(tag), ext_tag_(0), value_(), length_(0) + { + } + + basic_staj_event(bool value, semantic_tag tag) + : event_type_(staj_event_type::bool_value), tag_(tag), ext_tag_(0), length_(0) + { + value_.bool_value_ = value; + } + + basic_staj_event(int64_t value, semantic_tag tag) + : event_type_(staj_event_type::int64_value), tag_(tag), ext_tag_(0), length_(0) + { + value_.int64_value_ = value; + } + + basic_staj_event(uint64_t value, semantic_tag tag) + : event_type_(staj_event_type::uint64_value), tag_(tag), ext_tag_(0), length_(0) + { + value_.uint64_value_ = value; + } + + basic_staj_event(half_arg_t, uint16_t value, semantic_tag tag) + : event_type_(staj_event_type::half_value), tag_(tag), ext_tag_(0), length_(0) + { + value_.half_value_ = value; + } + + basic_staj_event(double value, semantic_tag tag) + : event_type_(staj_event_type::double_value), tag_(tag), ext_tag_(0), length_(0) + { + value_.double_value_ = value; + } + + basic_staj_event(const string_view_type& s, + staj_event_type event_type, + semantic_tag tag = semantic_tag::none) + : event_type_(event_type), tag_(tag), ext_tag_(0), length_(s.length()) + { + value_.string_data_ = s.data(); + } + + basic_staj_event(const byte_string_view& s, + staj_event_type event_type, + semantic_tag tag = semantic_tag::none) + : event_type_(event_type), tag_(tag), ext_tag_(0), length_(s.size()) + { + value_.byte_string_data_ = s.data(); + } + + basic_staj_event(const byte_string_view& s, + staj_event_type event_type, + uint64_t ext_tag) + : event_type_(event_type), tag_(semantic_tag::ext), ext_tag_(ext_tag), length_(s.size()) + { + value_.byte_string_data_ = s.data(); + } + + std::size_t size() const + { + return length_; + } + + template + T get() const + { + std::error_code ec; + T val = get(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + return val; + } + + template + T get(std::error_code& ec) const + { + return get_(std::allocator{}, ec); + } + + template + typename std::enable_if::value && std::is_same::value, T>::type + get_(Allocator,std::error_code& ec) const + { + switch (event_type_) + { + case staj_event_type::key: + case staj_event_type::string_value: + { + value_converter,T> converter; + return converter.convert(jsoncons::basic_string_view(value_.string_data_, length_), tag(), ec); + } + case staj_event_type::byte_string_value: + { + value_converter converter; + return converter.convert(byte_string_view(value_.byte_string_data_,length_),tag(),ec); + } + case staj_event_type::uint64_value: + { + value_converter converter; + return converter.convert(value_.uint64_value_, tag(), ec); + } + case staj_event_type::int64_value: + { + value_converter converter; + return converter.convert(value_.int64_value_, tag(), ec); + } + case staj_event_type::half_value: + { + value_converter converter; + return converter.convert(value_.half_value_, tag(), ec); + } + case staj_event_type::double_value: + { + value_converter converter; + return converter.convert(value_.double_value_, tag(), ec); + } + case staj_event_type::bool_value: + { + value_converter converter; + return converter.convert(value_.bool_value_,tag(),ec); + } + case staj_event_type::null_value: + { + value_converter converter; + return converter.convert(tag(), ec); + } + default: + { + ec = conv_errc::not_string; + return T{}; + } + } + } + + template + typename std::enable_if::value && std::is_same::value, T>::type + get_(Allocator, std::error_code& ec) const + { + T s; + switch (event_type_) + { + case staj_event_type::key: + case staj_event_type::string_value: + s = T(value_.string_data_, length_); + break; + default: + ec = conv_errc::not_string_view; + break; + } + return s; + } + + template + typename std::enable_if::value, T>::type + get_(Allocator, std::error_code& ec) const + { + T s; + switch (event_type_) + { + case staj_event_type::byte_string_value: + s = T(value_.byte_string_data_, length_); + break; + default: + ec = conv_errc::not_byte_string_view; + break; + } + return s; + } + + template + typename std::enable_if::value && + std::is_same::value,T>::type + get_(Allocator, std::error_code& ec) const + { + switch (event_type_) + { + case staj_event_type::byte_string_value: + { + value_converter converter; + return converter.convert(byte_string_view(value_.byte_string_data_, length_), tag(), ec); + } + case staj_event_type::string_value: + { + value_converter,T> converter; + return converter.convert(jsoncons::basic_string_view(value_.string_data_, length_), tag(), ec); + } + default: + ec = conv_errc::not_byte_string; + return T{}; + } + } + + template + typename std::enable_if::value, IntegerType>::type + get_(Allocator, std::error_code& ec) const + { + switch (event_type_) + { + case staj_event_type::string_value: + { + IntegerType val; + auto result = jsoncons::detail::to_integer(value_.string_data_, length_, val); + if (!result) + { + ec = conv_errc::not_integer; + return IntegerType(); + } + return val; + } + case staj_event_type::half_value: + return static_cast(value_.half_value_); + case staj_event_type::double_value: + return static_cast(value_.double_value_); + case staj_event_type::int64_value: + return static_cast(value_.int64_value_); + case staj_event_type::uint64_value: + return static_cast(value_.uint64_value_); + case staj_event_type::bool_value: + return static_cast(value_.bool_value_ ? 1 : 0); + default: + ec = conv_errc::not_integer; + return IntegerType(); + } + } + + template + typename std::enable_if::value, T>::type + get_(Allocator, std::error_code& ec) const + { + return static_cast(as_double(ec)); + } + + template + typename std::enable_if::value, T>::type + get_(Allocator, std::error_code& ec) const + { + return as_bool(ec); + } + + staj_event_type event_type() const noexcept { return event_type_; } + + semantic_tag tag() const noexcept { return tag_; } + + uint64_t ext_tag() const noexcept { return ext_tag_; } + +private: + + double as_double(std::error_code& ec) const + { + switch (event_type_) + { + case staj_event_type::key: + case staj_event_type::string_value: + { + jsoncons::detail::chars_to f; + return f(value_.string_data_, length_); + } + case staj_event_type::double_value: + return value_.double_value_; + case staj_event_type::int64_value: + return static_cast(value_.int64_value_); + case staj_event_type::uint64_value: + return static_cast(value_.uint64_value_); + case staj_event_type::half_value: + { + double x = binary::decode_half(value_.half_value_); + return static_cast(x); + } + default: + ec = conv_errc::not_double; + return double(); + } + } + + bool as_bool(std::error_code& ec) const + { + switch (event_type_) + { + case staj_event_type::bool_value: + return value_.bool_value_; + case staj_event_type::double_value: + return value_.double_value_ != 0.0; + case staj_event_type::int64_value: + return value_.int64_value_ != 0; + case staj_event_type::uint64_value: + return value_.uint64_value_ != 0; + default: + ec = conv_errc::not_bool; + return bool(); + } + } +public: + bool send_json_event(basic_json_visitor& visitor, + const ser_context& context, + std::error_code& ec) const + { + switch (event_type()) + { + case staj_event_type::begin_array: + return visitor.begin_array(tag(), context); + case staj_event_type::end_array: + return visitor.end_array(context); + case staj_event_type::begin_object: + return visitor.begin_object(tag(), context, ec); + case staj_event_type::end_object: + return visitor.end_object(context, ec); + case staj_event_type::key: + return visitor.key(string_view_type(value_.string_data_,length_), context); + case staj_event_type::string_value: + return visitor.string_value(string_view_type(value_.string_data_,length_), tag(), context); + case staj_event_type::byte_string_value: + return visitor.byte_string_value(byte_string_view(value_.byte_string_data_,length_), tag(), context); + case staj_event_type::null_value: + return visitor.null_value(tag(), context); + case staj_event_type::bool_value: + return visitor.bool_value(value_.bool_value_, tag(), context); + case staj_event_type::int64_value: + return visitor.int64_value(value_.int64_value_, tag(), context); + case staj_event_type::uint64_value: + return visitor.uint64_value(value_.uint64_value_, tag(), context); + case staj_event_type::half_value: + return visitor.half_value(value_.half_value_, tag(), context); + case staj_event_type::double_value: + return visitor.double_value(value_.double_value_, tag(), context); + default: + return false; + } + } + + bool send_value_event(basic_item_event_visitor& visitor, + const ser_context& context, + std::error_code& ec) const + { + switch (event_type()) + { + case staj_event_type::key: + return visitor.string_value(string_view_type(value_.string_data_,length_), tag(), context); + case staj_event_type::begin_array: + return visitor.begin_array(tag(), context); + case staj_event_type::end_array: + return visitor.end_array(context); + case staj_event_type::begin_object: + return visitor.begin_object(tag(), context, ec); + case staj_event_type::end_object: + return visitor.end_object(context, ec); + case staj_event_type::string_value: + return visitor.string_value(string_view_type(value_.string_data_,length_), tag(), context); + case staj_event_type::byte_string_value: + return visitor.byte_string_value(byte_string_view(value_.byte_string_data_,length_), tag(), context); + case staj_event_type::null_value: + return visitor.null_value(tag(), context); + case staj_event_type::bool_value: + return visitor.bool_value(value_.bool_value_, tag(), context); + case staj_event_type::int64_value: + return visitor.int64_value(value_.int64_value_, tag(), context); + case staj_event_type::uint64_value: + return visitor.uint64_value(value_.uint64_value_, tag(), context); + case staj_event_type::half_value: + return visitor.half_value(value_.half_value_, tag(), context); + case staj_event_type::double_value: + return visitor.double_value(value_.double_value_, tag(), context); + default: + return false; + } + } + +}; + +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons/staj_event_reader.hpp b/third_party/jsoncons/staj_event_reader.hpp new file mode 100644 index 0000000000..0513ba3081 --- /dev/null +++ b/third_party/jsoncons/staj_event_reader.hpp @@ -0,0 +1,739 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_STAJ_EVENT_READER_HPP +#define JSONCONS_STAJ_EVENT_READER_HPP + +#include // std::allocator +#include +#include +#include +#include +#include // std::enable_if +#include // std::array +#include // std::function +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + + // basic_item_event_receiver + + enum class item_event_reader_state + { + typed_array = 1, + multi_dim, + shape + }; + + template + class basic_item_event_receiver : public basic_item_event_visitor + { + using super_type = basic_item_event_visitor; + public: + using char_type = CharT; + using typename super_type::string_view_type; + private: + std::function&, const ser_context&)> pred_; + basic_staj_event event_; + + item_event_reader_state state_; + typed_array_view data_; + jsoncons::span shape_; + std::size_t index_; + public: + basic_item_event_receiver() + : pred_(accept), event_(staj_event_type::null_value), + state_(), data_(), shape_(), index_(0) + { + } + + basic_item_event_receiver(std::function&, const ser_context&)> pred) + : pred_(pred), event_(staj_event_type::null_value), + state_(), data_(), shape_(), index_(0) + { + } + + void reset() + { + event_ = staj_event_type::null_value; + state_ = {}; + data_ = {}; + shape_ = {}; + index_ = 0; + } + + const basic_staj_event& event() const + { + return event_; + } + + bool in_available() const + { + return state_ != item_event_reader_state(); + } + + void send_available(std::error_code& ec) + { + switch (state_) + { + case item_event_reader_state::typed_array: + advance_typed_array(ec); + break; + case item_event_reader_state::multi_dim: + case item_event_reader_state::shape: + advance_multi_dim(ec); + break; + default: + break; + } + } + + bool is_typed_array() const + { + return data_.type() != typed_array_type(); + } + + item_event_reader_state state() const + { + return state_; + } + + void advance_typed_array(std::error_code& ec) + { + if (is_typed_array()) + { + if (index_ < data_.size()) + { + switch (data_.type()) + { + case typed_array_type::uint8_value: + { + this->uint64_value(data_.data(uint8_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::uint16_value: + { + this->uint64_value(data_.data(uint16_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::uint32_value: + { + this->uint64_value(data_.data(uint32_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::uint64_value: + { + this->uint64_value(data_.data(uint64_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int8_value: + { + this->int64_value(data_.data(int8_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int16_value: + { + this->int64_value(data_.data(int16_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int32_value: + { + this->int64_value(data_.data(int32_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::int64_value: + { + this->int64_value(data_.data(int64_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::half_value: + { + this->half_value(data_.data(half_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::float_value: + { + this->double_value(data_.data(float_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + case typed_array_type::double_value: + { + this->double_value(data_.data(double_array_arg)[index_], semantic_tag::none, ser_context(), ec); + break; + } + default: + break; + } + ++index_; + } + else + { + this->end_array(); + state_ = item_event_reader_state(); + data_ = typed_array_view(); + index_ = 0; + } + } + } + + void advance_multi_dim(std::error_code& ec) + { + if (shape_.size() != 0) + { + if (state_ == item_event_reader_state::multi_dim) + { + this->begin_array(shape_.size(), semantic_tag::none, ser_context(), ec); + state_ = item_event_reader_state::shape; + } + else if (index_ < shape_.size()) + { + this->uint64_value(shape_[index_], semantic_tag::none, ser_context(), ec); + ++index_; + } + else + { + state_ = item_event_reader_state(); + this->end_array(ser_context(), ec); + shape_ = jsoncons::span(); + index_ = 0; + } + } + } + + bool dump(basic_item_event_visitor& visitor, const ser_context& context, std::error_code& ec) + { + bool more = true; + if (is_typed_array()) + { + if (index_ != 0) + { + more = event().send_value_event(visitor, context, ec); + while (more && is_typed_array()) + { + if (index_ < data_.size()) + { + switch (data_.type()) + { + case typed_array_type::uint8_value: + { + more = visitor.uint64_value(data_.data(uint8_array_arg)[index_]); + break; + } + case typed_array_type::uint16_value: + { + more = visitor.uint64_value(data_.data(uint16_array_arg)[index_]); + break; + } + case typed_array_type::uint32_value: + { + more = visitor.uint64_value(data_.data(uint32_array_arg)[index_]); + break; + } + case typed_array_type::uint64_value: + { + more = visitor.uint64_value(data_.data(uint64_array_arg)[index_]); + break; + } + case typed_array_type::int8_value: + { + more = visitor.int64_value(data_.data(int8_array_arg)[index_]); + break; + } + case typed_array_type::int16_value: + { + more = visitor.int64_value(data_.data(int16_array_arg)[index_]); + break; + } + case typed_array_type::int32_value: + { + more = visitor.int64_value(data_.data(int32_array_arg)[index_]); + break; + } + case typed_array_type::int64_value: + { + more = visitor.int64_value(data_.data(int64_array_arg)[index_]); + break; + } + case typed_array_type::float_value: + { + more = visitor.double_value(data_.data(float_array_arg)[index_]); + break; + } + case typed_array_type::double_value: + { + more = visitor.double_value(data_.data(double_array_arg)[index_]); + break; + } + default: + break; + } + ++index_; + } + else + { + more = visitor.end_array(); + state_ = item_event_reader_state(); + data_ = typed_array_view(); + index_ = 0; + } + } + } + else + { + switch (data_.type()) + { + case typed_array_type::uint8_value: + { + more = visitor.typed_array(data_.data(uint8_array_arg)); + break; + } + case typed_array_type::uint16_value: + { + more = visitor.typed_array(data_.data(uint16_array_arg)); + break; + } + case typed_array_type::uint32_value: + { + more = visitor.typed_array(data_.data(uint32_array_arg)); + break; + } + case typed_array_type::uint64_value: + { + more = visitor.typed_array(data_.data(uint64_array_arg)); + break; + } + case typed_array_type::int8_value: + { + more = visitor.typed_array(data_.data(int8_array_arg)); + break; + } + case typed_array_type::int16_value: + { + more = visitor.typed_array(data_.data(int16_array_arg)); + break; + } + case typed_array_type::int32_value: + { + more = visitor.typed_array(data_.data(int32_array_arg)); + break; + } + case typed_array_type::int64_value: + { + more = visitor.typed_array(data_.data(int64_array_arg)); + break; + } + case typed_array_type::float_value: + { + more = visitor.typed_array(data_.data(float_array_arg)); + break; + } + case typed_array_type::double_value: + { + more = visitor.typed_array(data_.data(double_array_arg)); + break; + } + default: + break; + } + + state_ = item_event_reader_state(); + data_ = typed_array_view(); + } + } + else + { + more = event().send_value_event(visitor, context, ec); + } + return more; + } + + private: + static constexpr bool accept(const basic_staj_event&, const ser_context&) + { + return true; + } + + bool visit_begin_object(semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_object, tag); + return !pred_(event_, context); + } + + bool visit_begin_object(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_object, length, tag); + return !pred_(event_, context); + } + + bool visit_end_object(const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::end_object); + return !pred_(event_, context); + } + + bool visit_begin_array(semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_array, tag); + return !pred_(event_, context); + } + + bool visit_begin_array(std::size_t length, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::begin_array, length, tag); + return !pred_(event_, context); + } + + bool visit_end_array(const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::end_array); + return !pred_(event_, context); + } + + bool visit_null(semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(staj_event_type::null_value, tag); + return !pred_(event_, context); + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_string(const string_view_type& s, semantic_tag tag, const ser_context& context, std::error_code&) override + { + event_ = basic_staj_event(s, staj_event_type::string_value, tag); + return !pred_(event_, context); + } + + bool visit_byte_string(const byte_string_view& s, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(s, staj_event_type::byte_string_value, tag); + return !pred_(event_, context); + } + + bool visit_byte_string(const byte_string_view& s, + uint64_t ext_tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(s, staj_event_type::byte_string_value, ext_tag); + return !pred_(event_, context); + } + + bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_half(uint16_t value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(half_arg, value, tag); + return !pred_(event_, context); + } + + bool visit_double(double value, + semantic_tag tag, + const ser_context& context, + std::error_code&) override + { + event_ = basic_staj_event(value, tag); + return !pred_(event_, context); + } + + bool visit_typed_array(const jsoncons::span& v, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(v.data(), v.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(half_arg_t, const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::typed_array; + data_ = typed_array_view(data.data(), data.size()); + index_ = 0; + return this->begin_array(tag, context, ec); + } + /* + bool visit_typed_array(const jsoncons::span&, + semantic_tag, + const ser_context&, + std::error_code&) override + { + return true; + } + */ + bool visit_begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + state_ = item_event_reader_state::multi_dim; + shape_ = shape; + return this->begin_array(2, tag, context, ec); + } + + bool visit_end_multi_dim(const ser_context& context, + std::error_code& ec) override + { + return this->end_array(context, ec); + } + + void visit_flush() override + { + } + }; + + // basic_staj_event_reader + + template + class basic_staj_event_reader + { + public: + virtual ~basic_staj_event_reader() noexcept = default; + + virtual void array_expected(std::error_code& ec) + { + if (!(current().event_type() == staj_event_type::begin_array || current().event_type() == staj_event_type::byte_string_value)) + { + ec = conv_errc::not_vector; + } + } + + virtual bool done() const = 0; + + virtual const basic_staj_event& current() const = 0; + + virtual void read_to(basic_item_event_visitor& visitor) = 0; + + virtual void read_to(basic_item_event_visitor& visitor, + std::error_code& ec) = 0; + + virtual void next() = 0; + + virtual void next(std::error_code& ec) = 0; + + virtual const ser_context& context() const = 0; + }; + + template + class basic_staj2_filter_view : basic_staj_event_reader + { + basic_staj_event_reader* cursor_; + std::function&, const ser_context&)> pred_; + public: + basic_staj2_filter_view(basic_staj_event_reader& cursor, + std::function&, const ser_context&)> pred) + : cursor_(std::addressof(cursor)), pred_(pred) + { + while (!done() && !pred_(current(),context())) + { + cursor_->next(); + } + } + + bool done() const override + { + return cursor_->done(); + } + + const basic_staj_event& current() const override + { + return cursor_->current(); + } + + void read_to(basic_item_event_visitor& visitor) override + { + cursor_->read_to(visitor); + } + + void read_to(basic_item_event_visitor& visitor, + std::error_code& ec) override + { + cursor_->read_to(visitor, ec); + } + + void next() override + { + cursor_->next(); + while (!done() && !pred_(current(),context())) + { + cursor_->next(); + } + } + + void next(std::error_code& ec) override + { + cursor_->next(ec); + while (!done() && !pred_(current(),context()) && !ec) + { + cursor_->next(ec); + } + } + + const ser_context& context() const override + { + return cursor_->context(); + } + + friend + basic_staj2_filter_view operator|(basic_staj2_filter_view& cursor, + std::function&, const ser_context&)> pred) + { + return basic_staj2_filter_view(cursor, pred); + } + }; + + using item_event = basic_staj_event; + using witem_event = basic_staj_event; + + using staj_event_reader = basic_staj_event_reader; + using wstaj_event_reader = basic_staj_event_reader; + + using staj2_filter_view = basic_staj2_filter_view; + using wstaj2_filter_view = basic_staj2_filter_view; + +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons/staj_iterator.hpp b/third_party/jsoncons/staj_iterator.hpp new file mode 100644 index 0000000000..a657af99ac --- /dev/null +++ b/third_party/jsoncons/staj_iterator.hpp @@ -0,0 +1,429 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_STAJ_ITERATOR_HPP +#define JSONCONS_STAJ_ITERATOR_HPP + +#include // placement new +#include +#include +#include +#include +#include +#include // std::input_iterator_tag +#include +#include +#include +#include + +namespace jsoncons { + + template + class staj_array_view; + + template + class staj_array_iterator + { + using char_type = typename Json::char_type; + + staj_array_view* view_; + std::exception_ptr eptr_; + + public: + using value_type = T; + using difference_type = std::ptrdiff_t; + using pointer = T*; + using reference = T&; + using iterator_category = std::input_iterator_tag; + + staj_array_iterator() noexcept + : view_(nullptr) + { + } + + staj_array_iterator(staj_array_view& view) + : view_(std::addressof(view)) + { + if (view_->cursor_->current().event_type() == staj_event_type::begin_array) + { + next(); + } + else + { + view_->cursor_ = nullptr; + } + } + + staj_array_iterator(staj_array_view& view, + std::error_code& ec) + : view_(std::addressof(view)) + { + if (view_->cursor_->current().event_type() == staj_event_type::begin_array) + { + next(ec); + if (ec) {view_ = nullptr;} + } + else + { + view_ = nullptr; + } + } + + ~staj_array_iterator() noexcept + { + } + + bool has_value() const + { + return !eptr_; + } + + const T& operator*() const + { + if (eptr_) + { + std::rethrow_exception(eptr_); + } + else + { + return *view_->value_; + } + } + + const T* operator->() const + { + if (eptr_) + { + std::rethrow_exception(eptr_); + } + else + { + return view_->value_.operator->(); + } + } + + staj_array_iterator& operator++() + { + next(); + return *this; + } + + staj_array_iterator& increment(std::error_code& ec) + { + next(ec); + if (ec) {view_ = nullptr;} + return *this; + } + + staj_array_iterator operator++(int) // postfix increment + { + staj_array_iterator temp(*this); + next(); + return temp; + } + + friend bool operator==(const staj_array_iterator& a, const staj_array_iterator& b) + { + return (!a.view_ && !b.view_) + || (!a.view_ && b.done()) + || (!b.view_ && a.done()); + } + + friend bool operator!=(const staj_array_iterator& a, const staj_array_iterator& b) + { + return !(a == b); + } + + private: + + bool done() const + { + return view_->cursor_->done() || view_->cursor_->current().event_type() == staj_event_type::end_array; + } + + void next() + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, view_->cursor_->context().line(), view_->cursor_->context().column())); + } + } + + void next(std::error_code& ec) + { + if (!done()) + { + view_->cursor_->next(ec); + if (ec) + { + return; + } + if (!done()) + { + eptr_ = std::exception_ptr(); + JSONCONS_TRY + { + view_->value_ = decode_traits::decode(*view_->cursor_, view_->decoder_, ec); + } + JSONCONS_CATCH(const conv_error&) + { + eptr_ = std::current_exception(); + } + } + } + } + }; + + template + class staj_object_view; + + template + class staj_object_iterator + { + using char_type = typename Json::char_type; + + staj_object_view* view_; + std::exception_ptr eptr_; + public: + using key_type = std::basic_string; + using value_type = std::pair; + using difference_type = std::ptrdiff_t; + using pointer = value_type*; + using reference = value_type&; + using iterator_category = std::input_iterator_tag; + + public: + + staj_object_iterator() noexcept + : view_(nullptr) + { + } + + staj_object_iterator(staj_object_view& view) + : view_(std::addressof(view)) + { + if (view_->cursor_->current().event_type() == staj_event_type::begin_object) + { + next(); + } + else + { + view_ = nullptr; + } + } + + staj_object_iterator(staj_object_view& view, + std::error_code& ec) + : view_(std::addressof(view)) + { + if (view_->cursor_->current().event_type() == staj_event_type::begin_object) + { + next(ec); + if (ec) {view_ = nullptr;} + } + else + { + view_ = nullptr; + } + } + + ~staj_object_iterator() noexcept + { + } + + bool has_value() const + { + return !eptr_; + } + + const value_type& operator*() const + { + if (eptr_) + { + std::rethrow_exception(eptr_); + } + else + { + return *view_->key_value_; + } + } + + const value_type* operator->() const + { + if (eptr_) + { + std::rethrow_exception(eptr_); + } + else + { + return view_->key_value_.operator->(); + } + } + + staj_object_iterator& operator++() + { + next(); + return *this; + } + + staj_object_iterator& increment(std::error_code& ec) + { + next(ec); + if (ec) + { + view_ = nullptr; + } + return *this; + } + + staj_object_iterator operator++(int) // postfix increment + { + staj_object_iterator temp(*this); + next(); + return temp; + } + + friend bool operator==(const staj_object_iterator& a, const staj_object_iterator& b) + { + return (!a.view_ && !b.view_) + || (!a.view_ && b.done()) + || (!b.view_ && a.done()); + } + + friend bool operator!=(const staj_object_iterator& a, const staj_object_iterator& b) + { + return !(a == b); + } + + private: + + bool done() const + { + return view_->cursor_->done() || view_->cursor_->current().event_type() == staj_event_type::end_object; + } + + void next() + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, view_->cursor_->context().line(), view_->cursor_->context().column())); + } + } + + void next(std::error_code& ec) + { + view_->cursor_->next(ec); + if (ec) + { + return; + } + if (!done()) + { + JSONCONS_ASSERT(view_->cursor_->current().event_type() == staj_event_type::key); + auto key = view_->cursor_->current(). template get(); + view_->cursor_->next(ec); + if (ec) + { + return; + } + if (!done()) + { + eptr_ = std::exception_ptr(); + JSONCONS_TRY + { + view_->key_value_ = value_type(std::move(key),decode_traits::decode(*view_->cursor_, view_->decoder_, ec)); + } + JSONCONS_CATCH(const conv_error&) + { + eptr_ = std::current_exception(); + } + } + } + } + }; + + // staj_array_view + + template + class staj_array_view + { + friend class staj_array_iterator; + public: + using char_type = typename Json::char_type; + using iterator = staj_array_iterator; + private: + basic_staj_cursor* cursor_; + json_decoder decoder_; + jsoncons::optional value_; + public: + staj_array_view(basic_staj_cursor& cursor) + : cursor_(std::addressof(cursor)) + { + } + + iterator begin() + { + return staj_array_iterator(*this); + } + + iterator end() + { + return staj_array_iterator(); + } + }; + + // staj_object_view + + template + class staj_object_view + { + friend class staj_object_iterator; + public: + using char_type = typename Json::char_type; + using iterator = staj_object_iterator; + using key_type = std::basic_string; + using value_type = std::pair; + private: + basic_staj_cursor* cursor_; + json_decoder decoder_; + jsoncons::optional key_value_; + public: + staj_object_view(basic_staj_cursor& cursor) + : cursor_(std::addressof(cursor)) + { + } + + iterator begin() + { + return staj_object_iterator(*this); + } + + iterator end() + { + return staj_object_iterator(); + } + }; + + template ::value,T,basic_json>::type> + staj_array_view staj_array(basic_staj_cursor& cursor) + { + return staj_array_view(cursor); + } + + template ::value,T,basic_json>::type> + staj_object_view staj_object(basic_staj_cursor& cursor) + { + return staj_object_view(cursor); + } + +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons/tag_type.hpp b/third_party/jsoncons/tag_type.hpp new file mode 100644 index 0000000000..24111469c3 --- /dev/null +++ b/third_party/jsoncons/tag_type.hpp @@ -0,0 +1,232 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_TAG_TYPE_HPP +#define JSONCONS_TAG_TYPE_HPP + +#include +#include + +namespace jsoncons { + +struct null_type +{ + explicit null_type() = default; +}; + +struct temp_allocator_arg_t +{ + explicit temp_allocator_arg_t() = default; +}; + +constexpr temp_allocator_arg_t temp_allocator_arg{}; + +struct half_arg_t +{ + explicit half_arg_t() = default; +}; + +constexpr half_arg_t half_arg{}; + +struct json_array_arg_t +{ + explicit json_array_arg_t() = default; +}; + +constexpr json_array_arg_t json_array_arg{}; + +struct json_object_arg_t +{ + explicit json_object_arg_t() = default; +}; + +constexpr json_object_arg_t json_object_arg{}; + +struct byte_string_arg_t +{ + explicit byte_string_arg_t() = default; +}; + +constexpr byte_string_arg_t byte_string_arg{}; + +struct json_const_pointer_arg_t +{ + explicit json_const_pointer_arg_t() = default; +}; + +constexpr json_const_pointer_arg_t json_const_pointer_arg{}; + +enum class semantic_tag : uint8_t +{ + none = 0, // 00000000 + undefined = 1, // 00000001 + datetime = 2, // 00000010 + epoch_second = 3, // 00000011 + epoch_milli = 4, // 00000100 + epoch_nano = 5, // 00000101 + base16 = 6, // 00000110 + base64 = 7, // 00000111 + base64url = 8, // 00001000 + uri = 9, + multi_dim_row_major = 10, + multi_dim_column_major = 11, + bigint = 12, // 00001100 + bigdec = 13, // 00001101 + bigfloat = 14, // 00001110 + float128 = 15, // 00001111 + clamped = 16, + ext = 17, + id = 18, + regex = 19, + code = 20 +}; + +inline bool is_number_tag(semantic_tag tag) noexcept +{ + static const uint8_t mask{ uint8_t(semantic_tag::bigint) & uint8_t(semantic_tag::bigdec) + & uint8_t(semantic_tag::bigfloat) & uint8_t(semantic_tag::float128) }; + return (uint8_t(tag) & mask) == mask; +} + +template +std::basic_ostream& operator<<(std::basic_ostream& os, semantic_tag tag) +{ + static constexpr const CharT* na_name = JSONCONS_CSTRING_CONSTANT(CharT, "n/a"); + static constexpr const CharT* undefined_name = JSONCONS_CSTRING_CONSTANT(CharT, "undefined"); + static constexpr const CharT* datetime_name = JSONCONS_CSTRING_CONSTANT(CharT, "datetime"); + static constexpr const CharT* epoch_second_name = JSONCONS_CSTRING_CONSTANT(CharT, "epoch-second"); + static constexpr const CharT* epoch_milli_name = JSONCONS_CSTRING_CONSTANT(CharT, "epoch-milli"); + static constexpr const CharT* epoch_nano_name = JSONCONS_CSTRING_CONSTANT(CharT, "epoch-nano"); + static constexpr const CharT* bigint_name = JSONCONS_CSTRING_CONSTANT(CharT, "bigint"); + static constexpr const CharT* bigdec_name = JSONCONS_CSTRING_CONSTANT(CharT, "bigdec"); + static constexpr const CharT* bigfloat_name = JSONCONS_CSTRING_CONSTANT(CharT, "bigfloat"); + static constexpr const CharT* base16_name = JSONCONS_CSTRING_CONSTANT(CharT, "base16"); + static constexpr const CharT* base64_name = JSONCONS_CSTRING_CONSTANT(CharT, "base64"); + static constexpr const CharT* base64url_name = JSONCONS_CSTRING_CONSTANT(CharT, "base64url"); + static constexpr const CharT* uri_name = JSONCONS_CSTRING_CONSTANT(CharT, "uri"); + static constexpr const CharT* clamped_name = JSONCONS_CSTRING_CONSTANT(CharT, "clamped"); + static constexpr const CharT* multi_dim_row_major_name = JSONCONS_CSTRING_CONSTANT(CharT, "multi-dim-row-major"); + static constexpr const CharT* multi_dim_column_major_name = JSONCONS_CSTRING_CONSTANT(CharT, "multi-dim-column-major"); + static constexpr const CharT* ext_name = JSONCONS_CSTRING_CONSTANT(CharT, "ext"); + static constexpr const CharT* id_name = JSONCONS_CSTRING_CONSTANT(CharT, "id"); + static constexpr const CharT* float128_name = JSONCONS_CSTRING_CONSTANT(CharT, "float128"); + static constexpr const CharT* regex_name = JSONCONS_CSTRING_CONSTANT(CharT, "regex"); + static constexpr const CharT* code_name = JSONCONS_CSTRING_CONSTANT(CharT, "code"); + + switch (tag) + { + case semantic_tag::none: + { + os << na_name; + break; + } + case semantic_tag::undefined: + { + os << undefined_name; + break; + } + case semantic_tag::datetime: + { + os << datetime_name; + break; + } + case semantic_tag::epoch_second: + { + os << epoch_second_name; + break; + } + case semantic_tag::epoch_milli: + { + os << epoch_milli_name; + break; + } + case semantic_tag::epoch_nano: + { + os << epoch_nano_name; + break; + } + case semantic_tag::bigint: + { + os << bigint_name; + break; + } + case semantic_tag::bigdec: + { + os << bigdec_name; + break; + } + case semantic_tag::bigfloat: + { + os << bigfloat_name; + break; + } + case semantic_tag::float128: + { + os << float128_name; + break; + } + case semantic_tag::base16: + { + os << base16_name; + break; + } + case semantic_tag::base64: + { + os << base64_name; + break; + } + case semantic_tag::base64url: + { + os << base64url_name; + break; + } + case semantic_tag::uri: + { + os << uri_name; + break; + } + case semantic_tag::clamped: + { + os << clamped_name; + break; + } + case semantic_tag::multi_dim_row_major: + { + os << multi_dim_row_major_name; + break; + } + case semantic_tag::multi_dim_column_major: + { + os << multi_dim_column_major_name; + break; + } + case semantic_tag::ext: + { + os << ext_name; + break; + } + case semantic_tag::id: + { + os << id_name; + break; + } + case semantic_tag::regex: + { + os << regex_name; + break; + } + case semantic_tag::code: + { + os << code_name; + break; + } + } + return os; +} + +} + +#endif diff --git a/third_party/jsoncons/text_source_adaptor.hpp b/third_party/jsoncons/text_source_adaptor.hpp new file mode 100644 index 0000000000..6e91148b6c --- /dev/null +++ b/third_party/jsoncons/text_source_adaptor.hpp @@ -0,0 +1,144 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_TEXT_SOURCE_ADAPTOR_HPP +#define JSONCONS_TEXT_SOURCE_ADAPTOR_HPP + +#include +#include +#include +#include +#include +#include // std::allocator_traits +#include // std::vector +#include +#include // json_errc +#include +#include + +namespace jsoncons { + + // unicode_source_adaptor + + template + class unicode_source_adaptor + { + public: + using value_type = typename Source::value_type; + using source_type = Source; + private: + source_type source_; + bool bof_; + + + public: + template + unicode_source_adaptor(Sourceable&& source) + : source_(std::forward(source)), + bof_(true) + { + } + + bool is_error() const + { + return source_.is_error(); + } + + bool eof() const + { + return source_.eof(); + } + + span read_buffer(std::error_code& ec) + { + if (source_.eof()) + { + return span(); + } + + auto s = source_.read_buffer(); + const value_type* data = s.data(); + std::size_t length = s.size(); + + if (bof_ && length > 0) + { + auto r = unicode_traits::detect_encoding_from_bom(data, length); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + ec = json_errc::illegal_unicode_character; + return; + } + length -= (r.ptr - data); + data = r.ptr; + bof_ = false; + } + return span(data, length); + } + }; + + // json_source_adaptor + + template + class json_source_adaptor + { + public: + using value_type = typename Source::value_type; + using value_type = typename Source::value_type; + using source_type = Source; + private: + source_type source_; + bool bof_; + + public: + + template + json_source_adaptor(Sourceable&& source) + : source_(std::forward(source)), + bof_(true) + { + } + + bool is_error() const + { + return source_.is_error(); + } + + bool eof() const + { + return source_.eof(); + } + + span read_buffer(std::error_code& ec) + { + if (source_.eof()) + { + return span(); + } + + auto s = source_.read_buffer(); + const value_type* data = s.data(); + std::size_t length = s.size(); + + if (bof_ && length > 0) + { + auto r = unicode_traits::detect_json_encoding(data, length); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + ec = json_errc::illegal_unicode_character; + return span(); + } + length -= (r.ptr - data); + data = r.ptr; + bof_ = false; + } + return span(data, length); + } + }; + +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons/typed_array_view.hpp b/third_party/jsoncons/typed_array_view.hpp new file mode 100644 index 0000000000..3c4bd18a4c --- /dev/null +++ b/third_party/jsoncons/typed_array_view.hpp @@ -0,0 +1,250 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_TYPED_ARRAY_VIEW_HPP +#define JSONCONS_TYPED_ARRAY_VIEW_HPP + +#include // std::allocator +#include +#include +#include +#include +#include // std::enable_if +#include // std::array +#include // std::function +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + + struct uint8_array_arg_t {explicit uint8_array_arg_t() = default; }; + constexpr uint8_array_arg_t uint8_array_arg = uint8_array_arg_t(); + struct uint16_array_arg_t {explicit uint16_array_arg_t() = default; }; + struct uint32_array_arg_t {explicit uint32_array_arg_t() = default; }; + constexpr uint32_array_arg_t uint32_array_arg = uint32_array_arg_t(); + struct uint64_array_arg_t {explicit uint64_array_arg_t() = default; }; + constexpr uint64_array_arg_t uint64_array_arg = uint64_array_arg_t(); + struct int8_array_arg_t {explicit int8_array_arg_t() = default; }; + constexpr int8_array_arg_t int8_array_arg = int8_array_arg_t(); + struct int16_array_arg_t {explicit int16_array_arg_t() = default; }; + constexpr int16_array_arg_t int16_array_arg = int16_array_arg_t(); + struct int32_array_arg_t {explicit int32_array_arg_t() = default; }; + constexpr int32_array_arg_t int32_array_arg = int32_array_arg_t(); + struct int64_array_arg_t {explicit int64_array_arg_t() = default; }; + constexpr int64_array_arg_t int64_array_arg = int64_array_arg_t(); + constexpr uint16_array_arg_t uint16_array_arg = uint16_array_arg_t(); + struct half_array_arg_t {explicit half_array_arg_t() = default; }; + constexpr half_array_arg_t half_array_arg = half_array_arg_t(); + struct float_array_arg_t {explicit float_array_arg_t() = default; }; + constexpr float_array_arg_t float_array_arg = float_array_arg_t(); + struct double_array_arg_t {explicit double_array_arg_t() = default; }; + constexpr double_array_arg_t double_array_arg = double_array_arg_t(); + struct float128_array_arg_t {explicit float128_array_arg_t() = default; }; + constexpr float128_array_arg_t float128_array_arg = float128_array_arg_t(); + + enum class typed_array_type{uint8_value=1,uint16_value,uint32_value,uint64_value, + int8_value,int16_value,int32_value,int64_value, + half_value, float_value,double_value}; + + class typed_array_view + { + typed_array_type type_; + union + { + const uint8_t* uint8_data_; + const uint16_t* uint16_data_; + const uint32_t* uint32_data_; + const uint64_t* uint64_data_; + const int8_t* int8_data_; + const int16_t* int16_data_; + const int32_t* int32_data_; + const int64_t* int64_data_; + const float* float_data_; + const double* double_data_; + } data_; + std::size_t size_; + public: + + typed_array_view() + : type_(), data_(), size_(0) + { + } + + typed_array_view(const typed_array_view& other) + : type_(other.type_), data_(other.data_), size_(other.size()) + { + } + + typed_array_view(typed_array_view&& other) noexcept + { + swap(*this,other); + } + + typed_array_view(const uint8_t* data, std::size_t size) + : type_(typed_array_type::uint8_value), size_(size) + { + data_.uint8_data_ = data; + } + + typed_array_view(const uint16_t* data, std::size_t size) + : type_(typed_array_type::uint16_value), size_(size) + { + data_.uint16_data_ = data; + } + + typed_array_view(const uint32_t* data, std::size_t size) + : type_(typed_array_type::uint32_value), size_(size) + { + data_.uint32_data_ = data; + } + + typed_array_view(const uint64_t* data, std::size_t size) + : type_(typed_array_type::uint64_value), size_(size) + { + data_.uint64_data_ = data; + } + + typed_array_view(const int8_t* data, std::size_t size) + : type_(typed_array_type::int8_value), size_(size) + { + data_.int8_data_ = data; + } + + typed_array_view(const int16_t* data, std::size_t size) + : type_(typed_array_type::int16_value), size_(size) + { + data_.int16_data_ = data; + } + + typed_array_view(const int32_t* data, std::size_t size) + : type_(typed_array_type::int32_value), size_(size) + { + data_.int32_data_ = data; + } + + typed_array_view(const int64_t* data, std::size_t size) + : type_(typed_array_type::int64_value), size_(size) + { + data_.int64_data_ = data; + } + + typed_array_view(half_array_arg_t, const uint16_t* data, std::size_t size) + : type_(typed_array_type::half_value), size_(size) + { + data_.uint16_data_ = data; + } + + typed_array_view(const float* data, std::size_t size) + : type_(typed_array_type::float_value), size_(size) + { + data_.float_data_ = data; + } + + typed_array_view(const double* data, std::size_t size) + : type_(typed_array_type::double_value), size_(size) + { + data_.double_data_ = data; + } + + typed_array_view& operator=(const typed_array_view& other) + { + typed_array_view temp(other); + swap(*this,temp); + return *this; + } + + typed_array_type type() const {return type_;} + + std::size_t size() const + { + return size_; + } + + jsoncons::span data(uint8_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::uint8_value); + return jsoncons::span(data_.uint8_data_, size_); + } + + jsoncons::span data(uint16_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::uint16_value); + return jsoncons::span(data_.uint16_data_, size_); + } + + jsoncons::span data(uint32_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::uint32_value); + return jsoncons::span(data_.uint32_data_, size_); + } + + jsoncons::span data(uint64_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::uint64_value); + return jsoncons::span(data_.uint64_data_, size_); + } + + jsoncons::span data(int8_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::int8_value); + return jsoncons::span(data_.int8_data_, size_); + } + + jsoncons::span data(int16_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::int16_value); + return jsoncons::span(data_.int16_data_, size_); + } + + jsoncons::span data(int32_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::int32_value); + return jsoncons::span(data_.int32_data_, size_); + } + + jsoncons::span data(int64_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::int64_value); + return jsoncons::span(data_.int64_data_, size_); + } + + jsoncons::span data(half_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::half_value); + return jsoncons::span(data_.uint16_data_, size_); + } + + jsoncons::span data(float_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::float_value); + return jsoncons::span(data_.float_data_, size_); + } + + jsoncons::span data(double_array_arg_t) const + { + JSONCONS_ASSERT(type_ == typed_array_type::double_value); + return jsoncons::span(data_.double_data_, size_); + } + + friend void swap(typed_array_view& a, typed_array_view& b) noexcept + { + std::swap(a.data_,b.data_); + std::swap(a.type_,b.type_); + std::swap(a.size_,b.size_); + } + }; + +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons/unicode_traits.hpp b/third_party/jsoncons/unicode_traits.hpp new file mode 100644 index 0000000000..cc07302c44 --- /dev/null +++ b/third_party/jsoncons/unicode_traits.hpp @@ -0,0 +1,1330 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/unicode_traits for latest version + +/* + * Includes code derived from Unicode, Inc decomposition code in ConvertUTF.h and ConvertUTF.c + * http://www.unicode.org/ + * + * "Unicode, Inc. hereby grants the right to freely use the information + * supplied in this file in the creation of products supporting the + * Unicode Standard." +*/ + +#ifndef JSONCONS_UNICODE_TRAITS_HPP +#define JSONCONS_UNICODE_TRAITS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace unicode_traits { + + enum class encoding_kind {undetected,utf8,utf16le,utf16be,utf32le,utf32be}; + + inline + std::string to_string(encoding_kind encoding) + { + switch (encoding) + { + case encoding_kind::utf8: + return "utf8"; + case encoding_kind::utf16le: + return "utf16le"; + case encoding_kind::utf16be: + return "utf16be"; + case encoding_kind::utf32le: + return "utf32le"; + case encoding_kind::utf32be: + return "utf32be"; + default: + return "undetected"; + } + } + + template + struct detect_encoding_result + { + const Byte* ptr; + encoding_kind encoding; + }; + + template + typename std::enable_if::value,detect_encoding_result>::type + detect_encoding_from_bom(const CharT* data, std::size_t length) + { + const uint8_t bom_utf8[] = {0xef,0xbb,0xbf}; + const uint8_t bom_utf16le[] = {0xff,0xfe}; + const uint8_t bom_utf16be[] = {0xfe,0xff}; + const uint8_t bom_utf32le[] = {0xff,0xfe,0x00,0x00}; + const uint8_t bom_utf32be[] = {0x00,0x00,0xfe,0xff}; + + if (length >= 4 && !memcmp(data,bom_utf32le,4)) + { + return detect_encoding_result{data+4,encoding_kind::utf32le}; + } + else if (length >= 4 && !memcmp(data,bom_utf32be,4)) + { + return detect_encoding_result{data+4,encoding_kind::utf32be}; + } + else if (length >= 2 && !memcmp(data,bom_utf16le,2)) + { + return detect_encoding_result{data+2,encoding_kind::utf16le}; + } + else if (length >= 2 && !memcmp(data,bom_utf16be,2)) + { + return detect_encoding_result{data+2,encoding_kind::utf16be}; + } + else if (length >= 3 && !memcmp(data,bom_utf8,3)) + { + return detect_encoding_result{data+3,encoding_kind::utf8}; + } + else + { + return detect_encoding_result{data,encoding_kind::undetected}; + } + } + + template + typename std::enable_if::value || extension_traits::is_char32::value,detect_encoding_result>::type + detect_encoding_from_bom(const CharT* data, std::size_t) + { + return detect_encoding_result{data,encoding_kind::undetected}; + } + + template + typename std::enable_if::value,detect_encoding_result>::type + detect_json_encoding(const CharT* data, std::size_t length) + { + detect_encoding_result r = detect_encoding_from_bom(data,length); + if (r.encoding != encoding_kind::undetected) + { + return r; + } + else if (length < 4) + { + return detect_encoding_result{data,encoding_kind::utf8}; + } + else if (*data == 0 && *(data+1) == 0 && *(data+2) == 0) + { + return detect_encoding_result{data,encoding_kind::utf32be}; + } + else if (*data == 0 && *(data+2) == 0) + { + return detect_encoding_result{data,encoding_kind::utf16be}; + } + else if (*(data+1) == 0 && *(data+2) == 0 && *(data+3) == 0) + { + return detect_encoding_result{data,encoding_kind::utf32le}; + } + else if (*(data+1) == 0 && *(data+3) == 0) + { + return detect_encoding_result{data,encoding_kind::utf16le}; + } + else + { + return detect_encoding_result{data,encoding_kind::utf8}; + } + } + + template + typename std::enable_if::value || extension_traits::is_char32::value,detect_encoding_result>::type + detect_json_encoding(const CharT* data, std::size_t) + { + return detect_encoding_result{data,encoding_kind::undetected}; + } + + /* + * Magic values subtracted from a buffer value during UTF8 conversion. + * This table contains as many values as there might be trailing bytes + * in a UTF-8 sequence. Source: ConvertUTF.c + */ + const uint32_t offsets_from_utf8[6] = { 0x00000000UL, 0x00003080UL, 0x000E2080UL, + 0x03C82080UL, 0xFA082080UL, 0x82082080UL }; + + /* + * Once the bits are split out into bytes of UTF-8, this is a mask OR-ed + * into the first byte, depending on how many bytes follow. There are + * as many entries in this table as there are UTF-8 sequence types. + * (I.e., one byte sequence, two byte... etc.). Remember that sequencs + * for *legal* UTF-8 will be 4 or fewer bytes total. Source: ConvertUTF.c + */ + const uint8_t first_byte_mark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; + + /* + * Index into the table below with the first byte of a UTF-8 sequence to + * get the number of trailing bytes that are supposed to follow it. + * Note that *legal* UTF-8 values can't have 4 or 5-bytes. The table is + * left as-is for anyone who may want to do such conversion, which was + * allowed in earlier algorithms. Source: ConvertUTF.c + */ + const uint8_t trailing_bytes_for_utf8[256] = { + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, + 2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2, 3,3,3,3,3,3,3,3,4,4,4,4,5,5,5,5 + }; + + // Some fundamental constants. Source: ConvertUTF.h + const uint32_t replacement_char = 0x0000FFFD; + const uint32_t max_bmp = 0x0000FFFF; + const uint32_t max_utf16 = 0x0010FFFF; + const uint32_t max_utf32 = 0x7FFFFFFF; + const uint32_t max_legal_utf32 = 0x0010FFFF; + + const int half_shift = 10; // used for shifting by 10 bits + const uint32_t half_base = 0x0010000UL; + const uint32_t half_mask = 0x3FFUL; + + const uint16_t sur_high_start = 0xD800; + const uint16_t sur_high_end = 0xDBFF; + const uint16_t sur_low_start = 0xDC00; + const uint16_t sur_low_end = 0xDFFF; + + inline + static bool is_continuation_byte(unsigned char ch) + { + return (ch & 0xC0) == 0x80; + } + + inline + bool is_high_surrogate(uint32_t ch) noexcept + { + return (ch >= sur_high_start && ch <= sur_high_end); + } + + inline + bool is_low_surrogate(uint32_t ch) noexcept + { + return (ch >= sur_low_start && ch <= sur_low_end); + } + + inline + bool is_surrogate(uint32_t ch) noexcept + { + return (ch >= sur_high_start && ch <= sur_low_end); + } + + enum class conv_flags + { + strict = 0, + lenient + }; + + // conv_errc + + enum class conv_errc + { + success = 0, + over_long_utf8_sequence = 1, // over long utf8 sequence + expected_continuation_byte, // expected continuation byte + unpaired_high_surrogate, // unpaired high surrogate UTF-16 + illegal_surrogate_value, // UTF-16 surrogate values are illegal in UTF-32 + source_exhausted, // partial character in source, but hit end + source_illegal // source sequence is illegal/malformed + }; + + class Unicode_traits_error_category_impl_ + : public std::error_category + { + public: + virtual const char* name() const noexcept + { + return "unicode_traits conversion error"; + } + virtual std::string message(int ev) const + { + switch (static_cast(ev)) + { + case conv_errc::over_long_utf8_sequence: + return "Over long utf8 sequence"; + case conv_errc::expected_continuation_byte: + return "Expected continuation byte"; + case conv_errc::unpaired_high_surrogate: + return "Unpaired high surrogate UTF-16"; + case conv_errc::illegal_surrogate_value: + return "UTF-16 surrogate values are illegal in UTF-32"; + case conv_errc::source_exhausted: + return "Partial character in source, but hit end"; + case conv_errc::source_illegal: + return "Source sequence is illegal/malformed"; + default: + return ""; + break; + } + } + }; + + inline + const std::error_category& unicode_traits_error_category() + { + static Unicode_traits_error_category_impl_ instance; + return instance; + } + + inline + std::error_code make_error_code(conv_errc result) + { + return std::error_code(static_cast(result),unicode_traits_error_category()); + } + +} // unicode_traits +} // jsoncons + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +namespace jsoncons { namespace unicode_traits { + + // utf8 + + template + typename std::enable_if::value, conv_errc>::type + is_legal_utf8(const CharT* first, std::size_t length) + { + uint8_t a; + const CharT* srcptr = first+length; + switch (length) { + default: + return conv_errc::over_long_utf8_sequence; + case 4: + if (((a = (*--srcptr))& 0xC0) != 0x80) + return conv_errc::expected_continuation_byte; + JSONCONS_FALLTHROUGH; + case 3: + if (((a = (*--srcptr))& 0xC0) != 0x80) + return conv_errc::expected_continuation_byte; + JSONCONS_FALLTHROUGH; + case 2: + if (((a = (*--srcptr))& 0xC0) != 0x80) + return conv_errc::expected_continuation_byte; + + switch (static_cast(*first)) + { + // no fall-through in this inner switch + case 0xE0: if (a < 0xA0) return conv_errc::source_illegal; break; + case 0xED: if (a > 0x9F) return conv_errc::source_illegal; break; + case 0xF0: if (a < 0x90) return conv_errc::source_illegal; break; + case 0xF4: if (a > 0x8F) return conv_errc::source_illegal; break; + default: if (a < 0x80) return conv_errc::source_illegal; + } + + JSONCONS_FALLTHROUGH; + case 1: + if (static_cast(*first) >= 0x80 && static_cast(*first) < 0xC2) + return conv_errc::source_illegal; + break; + } + if (static_cast(*first) > 0xF4) + return conv_errc::source_illegal; + + return conv_errc(); + } + + template using void_t = void; + + template + struct is_output_iterator : std::false_type {}; + + template + struct is_output_iterator::iterator_category, + decltype(*std::declval() = std::declval())>> : std::true_type {}; + + // is_same_size fixes issue with vs2013 + + // primary template + template + struct is_same_size : std::false_type + { + }; + + // specialization for non void types + template + struct is_same_size::value && !std::is_void::value>::type> + { + static constexpr bool value = (sizeof(T1) == sizeof(T2)); + }; + + // convert + + template + struct convert_result + { + const CharT* ptr; + conv_errc ec; + }; + + // to_codepoint + + template + typename std::enable_if::value && extension_traits::is_char32::value, + convert_result>::type + to_codepoint(const CharT* first, const CharT* last, + CodepointT& ch, + conv_flags flags = conv_flags::strict) noexcept + { + ch = 0; + if (first >= last) + { + return convert_result{first, conv_errc::source_exhausted}; + } + conv_errc result = conv_errc(); + + unsigned short extra_bytes_to_read = trailing_bytes_for_utf8[static_cast(*first)]; + if (extra_bytes_to_read >= last - first) + { + result = conv_errc::source_exhausted; + return convert_result{first, result}; + } + // Do this check whether lenient or strict + if ((result=is_legal_utf8(first, extra_bytes_to_read+1)) != conv_errc()) + { + return convert_result{first, result}; + } + // The cases all fall through. See "Note A" below. + switch (extra_bytes_to_read) + { + case 5: + ch += static_cast(*first++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 4: + ch += static_cast(*first++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 3: + ch += static_cast(*first++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 2: + ch += static_cast(*first++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 1: + ch += static_cast(*first++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 0: + ch += static_cast(*first++); + break; + } + ch -= offsets_from_utf8[extra_bytes_to_read]; + + if (ch <= max_legal_utf32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (is_surrogate(ch) ) + { + if (flags == conv_flags::strict) + { + first -= (extra_bytes_to_read+1); // return to the illegal value itself + result = conv_errc::source_illegal; + return convert_result{first, result}; + } + else + { + ch = replacement_char; + } + } + } + else // i.e., ch > max_legal_utf32 + { + result = conv_errc::source_illegal; + ch = replacement_char; + } + + return convert_result{first,result} ; + } + + template + typename std::enable_if::value && extension_traits::is_char32::value, + convert_result>::type + to_codepoint(const CharT* first, const CharT* last, + CodepointT& ch, + conv_flags flags = conv_flags::strict) noexcept + { + ch = 0; + if (first >= last) + { + return convert_result{first, conv_errc::source_exhausted}; + } + conv_errc result = conv_errc(); + + ch = *first++; + // If we have a surrogate pair, convert to UTF32 first. + if (is_high_surrogate(ch)) + { + // If the 16 bits following the high surrogate are in the first buffer... + if (first < last) + { + uint32_t ch2 = *first; + // If ptr's a low surrogate, convert to UTF32. + if (ch2 >= sur_low_start && ch2 <= sur_low_end ) + { + ch = ((ch - sur_high_start) << half_shift) + + (ch2 - sur_low_start) + half_base; + ++first; + } + else if (flags == conv_flags::strict) // ptr's an unpaired high surrogate + { + --first; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + return convert_result{first, result}; + } + } + else + { /* We don't have the 16 bits following the high surrogate. */ + --first; /* return to the high surrogate */ + result = conv_errc::source_exhausted; + return convert_result{first, result}; + } + } else if (flags == conv_flags::strict) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_low_surrogate(ch) ) + { + --first; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + return convert_result{first, result}; + } + } + + return convert_result{first,result} ; + } + + template + typename std::enable_if::value && extension_traits::is_char32::value, + convert_result>::type + to_codepoint(const CharT* first, const CharT* last, + CodepointT& ch, + conv_flags flags = conv_flags::strict) noexcept + { + ch = 0; + if (first >= last) + { + return convert_result{first, conv_errc::source_exhausted}; + } + conv_errc result = conv_errc(); + + ch = *first++; + if (flags == conv_flags::strict ) + { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_surrogate(ch)) + { + --first; /* return to the illegal value itself */ + result = conv_errc::illegal_surrogate_value; + return convert_result{first,result} ; + } + } + if (!(ch <= max_legal_utf32)) + { + ch = replacement_char; + result = conv_errc::source_illegal; + } + + return convert_result{first,result} ; + } + + // convert + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char8::value, + convert_result>::type + convert(const CharT* data, std::size_t length, Container& target, conv_flags flags=conv_flags::strict) + { + (void)flags; + + conv_errc result = conv_errc(); + const CharT* last = data + length; + while (data != last) + { + std::size_t len = trailing_bytes_for_utf8[static_cast(*data)] + 1; + if (len > (std::size_t)(last - data)) + { + return convert_result{data, conv_errc::source_exhausted}; + } + if ((result=is_legal_utf8(data, len)) != conv_errc()) + { + return convert_result{data,result}; + } + + switch (len) { + case 4: target.push_back(static_cast(*data++)); + JSONCONS_FALLTHROUGH; + case 3: target.push_back(static_cast(*data++)); + JSONCONS_FALLTHROUGH; + case 2: target.push_back(static_cast(*data++)); + JSONCONS_FALLTHROUGH; + case 1: target.push_back(static_cast(*data++)); + } + } + return convert_result{data,result} ; + } + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char16::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data != last) + { + unsigned short extra_bytes_to_read = trailing_bytes_for_utf8[static_cast(*data)]; + if (extra_bytes_to_read >= last - data) + { + result = conv_errc::source_exhausted; + break; + } + /* Do this check whether lenient or strict */ + if ((result=is_legal_utf8(data, extra_bytes_to_read+1)) != conv_errc()) + { + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + uint32_t ch = 0; + switch (extra_bytes_to_read) { + case 5: ch += static_cast(*data++); ch <<= 6; /* remember, illegal UTF-8 */ + JSONCONS_FALLTHROUGH; + case 4: ch += static_cast(*data++); ch <<= 6; /* remember, illegal UTF-8 */ + JSONCONS_FALLTHROUGH; + case 3: ch += static_cast(*data++); ch <<= 6; + JSONCONS_FALLTHROUGH; + case 2: ch += static_cast(*data++); ch <<= 6; + JSONCONS_FALLTHROUGH; + case 1: ch += static_cast(*data++); ch <<= 6; + JSONCONS_FALLTHROUGH; + case 0: ch += static_cast(*data++); + break; + } + ch -= offsets_from_utf8[extra_bytes_to_read]; + + if (ch <= max_bmp) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_surrogate(ch) ) + { + if (flags == conv_flags::strict) { + data -= (extra_bytes_to_read+1); /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } else { + target.push_back(replacement_char); + } + } else { + target.push_back((uint16_t)ch); /* normal case */ + } + } else if (ch > max_utf16) { + if (flags == conv_flags::strict) { + result = conv_errc::source_illegal; + data -= (extra_bytes_to_read+1); /* return to the start */ + break; /* Bail out; shouldn't continue */ + } else { + target.push_back(replacement_char); + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + ch -= half_base; + target.push_back((uint16_t)((ch >> half_shift) + sur_high_start)); + target.push_back((uint16_t)((ch & half_mask) + sur_low_start)); + } + } + return convert_result{data,result} ; + } + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char32::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data < last) + { + uint32_t ch = 0; + unsigned short extra_bytes_to_read = trailing_bytes_for_utf8[static_cast(*data)]; + if (extra_bytes_to_read >= last - data) + { + result = conv_errc::source_exhausted; + break; + } + /* Do this check whether lenient or strict */ + if ((result=is_legal_utf8(data, extra_bytes_to_read+1)) != conv_errc()) + { + break; + } + /* + * The cases all fall through. See "Note A" below. + */ + switch (extra_bytes_to_read) + { + case 5: + ch += static_cast(*data++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 4: + ch += static_cast(*data++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 3: + ch += static_cast(*data++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 2: + ch += static_cast(*data++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 1: + ch += static_cast(*data++); + ch <<= 6; + JSONCONS_FALLTHROUGH; + case 0: + ch += static_cast(*data++); + break; + } + ch -= offsets_from_utf8[extra_bytes_to_read]; + + if (ch <= max_legal_utf32) { + /* + * UTF-16 surrogate values are illegal in UTF-32, and anything + * over Plane 17 (> 0x10FFFF) is illegal. + */ + if (is_surrogate(ch) ) + { + if (flags == conv_flags::strict) { + data -= (extra_bytes_to_read+1); /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } else { + target.push_back(replacement_char); + } + } else { + target.push_back(ch); + } + } else { /* i.e., ch > max_legal_utf32 */ + result = conv_errc::source_illegal; + target.push_back(replacement_char); + } + } + return convert_result{data,result} ; + } + + // utf16 + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char8::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data < last) { + unsigned short bytes_to_write = 0; + const uint32_t byteMask = 0xBF; + const uint32_t byteMark = 0x80; + uint32_t ch = *data++; + /* If we have a surrogate pair, convert to uint32_t data. */ + if (is_high_surrogate(ch)) + { + /* If the 16 bits following the high surrogate are in the data buffer... */ + if (data < last) { + uint32_t ch2 = *data; + /* If ptr's a low surrogate, convert to uint32_t. */ + if (ch2 >= sur_low_start && ch2 <= sur_low_end) { + ch = ((ch - sur_high_start) << half_shift) + + (ch2 - sur_low_start) + half_base; + ++data; + } else if (flags == conv_flags::strict) { /* ptr's an unpaired high surrogate */ + --data; /* return to the illegal value itself */ + result = conv_errc::unpaired_high_surrogate; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --data; /* return to the high surrogate */ + result = conv_errc::source_exhausted; + break; + } + } else if (flags == conv_flags::strict) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_low_surrogate(ch)) + { + --data; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } + } + /* Figure out how many bytes the result will require */ + if (ch < (uint32_t)0x80) { + bytes_to_write = 1; + } else if (ch < (uint32_t)0x800) { + bytes_to_write = 2; + } else if (ch < (uint32_t)0x10000) { + bytes_to_write = 3; + } else if (ch < (uint32_t)0x110000) { + bytes_to_write = 4; + } else { + bytes_to_write = 3; + ch = replacement_char; + } + + uint8_t byte1 = 0; + uint8_t byte2 = 0; + uint8_t byte3 = 0; + uint8_t byte4 = 0; + + switch (bytes_to_write) { // note: everything falls through + case 4: byte4 = (uint8_t)((ch | byteMark) & byteMask); ch >>= 6; + JSONCONS_FALLTHROUGH; + case 3: byte3 = (uint8_t)((ch | byteMark) & byteMask); ch >>= 6; + JSONCONS_FALLTHROUGH; + case 2: byte2 = (uint8_t)((ch | byteMark) & byteMask); ch >>= 6; + JSONCONS_FALLTHROUGH; + case 1: byte1 = (uint8_t)(ch | first_byte_mark[bytes_to_write]); + break; + } + switch (bytes_to_write) + { + case 4: + target.push_back(byte1); + target.push_back(byte2); + target.push_back(byte3); + target.push_back(byte4); + break; + case 3: + target.push_back(byte1); + target.push_back(byte2); + target.push_back(byte3); + break; + case 2: + target.push_back(byte1); + target.push_back(byte2); + break; + case 1: + target.push_back(byte1); + break; + } + } + return convert_result{data,result} ; + } + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char16::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data != last) + { + uint32_t ch = *data++; + /* If we have a surrogate pair, convert to uint32_t data. */ + if (is_high_surrogate(ch)) + { + /* If the 16 bits following the high surrogate are in the data buffer... */ + if (data < last) { + uint32_t ch2 = *data; + /* If ptr's a low surrogate, */ + if (ch2 >= sur_low_start && ch2 <= sur_low_end) { + target.push_back((uint16_t)ch); + target.push_back((uint16_t)ch2); + ++data; + } else if (flags == conv_flags::strict) { /* ptr's an unpaired high surrogate */ + --data; /* return to the illegal value itself */ + result = conv_errc::unpaired_high_surrogate; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --data; /* return to the high surrogate */ + result = conv_errc::source_exhausted; + break; + } + } else if (is_low_surrogate(ch)) + { + // illegal leading low surrogate + if (flags == conv_flags::strict) { + --data; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } + else + { + target.push_back((uint16_t)ch); + } + } + else + { + target.push_back((uint16_t)ch); + } + } + return convert_result{data,result} ; + } + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char32::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data != last) + { + uint32_t ch = *data++; + /* If we have a surrogate pair, convert to UTF32 data. */ + if (is_high_surrogate(ch)) + { + /* If the 16 bits following the high surrogate are in the data buffer... */ + if (data < last) { + uint32_t ch2 = *data; + /* If ptr's a low surrogate, convert to UTF32. */ + if (ch2 >= sur_low_start && ch2 <= sur_low_end ) + { + ch = ((ch - sur_high_start) << half_shift) + + (ch2 - sur_low_start) + half_base; + ++data; + } else if (flags == conv_flags::strict) { /* ptr's an unpaired high surrogate */ + --data; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } + } else { /* We don't have the 16 bits following the high surrogate. */ + --data; /* return to the high surrogate */ + result = conv_errc::source_exhausted; + break; + } + } else if (flags == conv_flags::strict) { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_low_surrogate(ch) ) + { + --data; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } + } + target.push_back(ch); + } + return convert_result{data,result} ; + } + + // utf32 + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char8::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) + { + conv_errc result = conv_errc(); + const CharT* last = data + length; + while (data < last) + { + unsigned short bytes_to_write = 0; + const uint32_t byteMask = 0xBF; + const uint32_t byteMark = 0x80; + uint32_t ch = *data++; + if (flags == conv_flags::strict ) + { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_surrogate(ch)) + { + --data; /* return to the illegal value itself */ + result = conv_errc::illegal_surrogate_value; + break; + } + } + /* + * Figure out how many bytes the result will require. Turn any + * illegally large UTF32 things (> Plane 17) into replacement chars. + */ + if (ch < (uint32_t)0x80) { bytes_to_write = 1; + } else if (ch < (uint32_t)0x800) { bytes_to_write = 2; + } else if (ch < (uint32_t)0x10000) { bytes_to_write = 3; + } else if (ch <= max_legal_utf32) { bytes_to_write = 4; + } else { + bytes_to_write = 3; + ch = replacement_char; + result = conv_errc::source_illegal; + } + + uint8_t byte1 = 0; + uint8_t byte2 = 0; + uint8_t byte3 = 0; + uint8_t byte4 = 0; + + switch (bytes_to_write) { + case 4: + byte4 = (uint8_t)((ch | byteMark) & byteMask); ch >>= 6; + JSONCONS_FALLTHROUGH; + case 3: + byte3 = (uint8_t)((ch | byteMark) & byteMask); ch >>= 6; + JSONCONS_FALLTHROUGH; + case 2: + byte2 = (uint8_t)((ch | byteMark) & byteMask); ch >>= 6; + JSONCONS_FALLTHROUGH; + case 1: + byte1 = (uint8_t) (ch | first_byte_mark[bytes_to_write]); + break; + } + + switch (bytes_to_write) + { + case 4: + target.push_back(byte1); + target.push_back(byte2); + target.push_back(byte3); + target.push_back(byte4); + break; + case 3: + target.push_back(byte1); + target.push_back(byte2); + target.push_back(byte3); + break; + case 2: + target.push_back(byte1); + target.push_back(byte2); + break; + case 1: + target.push_back(byte1); + break; + } + } + return convert_result{data,result} ; + } + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char16::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data != last) + { + uint32_t ch = *data++; + if (ch <= max_bmp) { /* Target is a character <= 0xFFFF */ + /* UTF-16 surrogate values are illegal in UTF-32; 0xffff or 0xfffe are both reserved values */ + if (is_surrogate(ch) ) + { + if (flags == conv_flags::strict) { + --data; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } else { + target.push_back(replacement_char); + } + } else { + target.push_back((uint16_t)ch); /* normal case */ + } + } else if (ch > max_legal_utf32) { + if (flags == conv_flags::strict) { + result = conv_errc::source_illegal; + } else { + target.push_back(replacement_char); + } + } else { + /* target is a character in range 0xFFFF - 0x10FFFF. */ + ch -= half_base; + target.push_back((uint16_t)((ch >> half_shift) + sur_high_start)); + target.push_back((uint16_t)((ch & half_mask) + sur_low_start)); + } + } + return convert_result{data,result} ; + } + + template + typename std::enable_if::value + && extension_traits::is_back_insertable::value + && extension_traits::is_char32::value, + convert_result>::type + convert(const CharT* data, std::size_t length, + Container& target, + conv_flags flags = conv_flags::strict) + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data != last) + { + uint32_t ch = *data++; + if (flags == conv_flags::strict ) + { + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_surrogate(ch)) + { + --data; /* return to the illegal value itself */ + result = conv_errc::illegal_surrogate_value; + break; + } + } + if (ch <= max_legal_utf32) + { + target.push_back(ch); + } + else + { + target.push_back(replacement_char); + result = conv_errc::source_illegal; + } + } + return convert_result{data,result} ; + } + + // validate + + template + typename std::enable_if::value, + convert_result>::type + validate(const CharT* data, std::size_t length) noexcept + { + conv_errc result = conv_errc(); + const CharT* last = data + length; + while (data != last) + { + std::size_t len = static_cast(trailing_bytes_for_utf8[static_cast(*data)]) + 1; + if (len > (std::size_t)(last - data)) + { + return convert_result{data, conv_errc::source_exhausted}; + } + if ((result=is_legal_utf8(data, len)) != conv_errc()) + { + return convert_result{data,result} ; + } + data += len; + } + return convert_result{data,result} ; + } + + // utf16 + + template + typename std::enable_if::value, + convert_result>::type + validate(const CharT* data, std::size_t length) noexcept + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data != last) + { + uint32_t ch = *data++; + /* If we have a surrogate pair, validate to uint32_t data. */ + if (is_high_surrogate(ch)) + { + /* If the 16 bits following the high surrogate are in the data buffer... */ + if (data < last) { + uint32_t ch2 = *data; + /* If ptr's a low surrogate, */ + if (ch2 >= sur_low_start && ch2 <= sur_low_end) { + ++data; + } else { + --data; /* return to the illegal value itself */ + result = conv_errc::unpaired_high_surrogate; + break; + } + } + else // We don't have the 16 bits following the high surrogate. + { + --data; /* return to the high surrogate */ + result = conv_errc::source_exhausted; + break; + } + } + else if (is_low_surrogate(ch)) + { + /* UTF-16 surrogate values are illegal in UTF-32 */ + --data; /* return to the illegal value itself */ + result = conv_errc::source_illegal; + break; + } + } + return convert_result{data,result} ; + } + + // utf32 + + template + typename std::enable_if::value, + convert_result>::type + validate(const CharT* data, std::size_t length) noexcept + { + conv_errc result = conv_errc(); + + const CharT* last = data + length; + while (data != last) + { + uint32_t ch = *data++; + /* UTF-16 surrogate values are illegal in UTF-32 */ + if (is_surrogate(ch)) + { + --data; /* return to the illegal value itself */ + result = conv_errc::illegal_surrogate_value; + break; + } + if (!(ch <= max_legal_utf32)) + { + result = conv_errc::source_illegal; + } + } + return convert_result{data, result} ; + } + + enum class encoding {u8,u16le,u16be,u32le,u32be,undetected}; + + template + struct determine_encoding_result + { + Iterator it; + encoding ec; + }; + + template + typename std::enable_if::value_type>::value && sizeof(typename std::iterator_traits::value_type) == sizeof(uint8_t), + determine_encoding_result>::type + detect_encoding(Iterator first, Iterator last) noexcept + { + Iterator it1 = first; + if (std::distance(first,last) < 4) + { + if (std::distance(first,last) == 3) + { + Iterator it2 = ++first; + Iterator it3 = ++first; + if (static_cast(*it1) == 0xEF && static_cast(*it2) == 0xBB && static_cast(*it3) == 0xBF) + { + return determine_encoding_result{last,encoding::u8}; + } + } + return determine_encoding_result{it1,encoding::undetected}; + } + else + { + Iterator it2 = ++first; + Iterator it3 = ++first; + Iterator it4 = ++first; + + uint32_t bom = static_cast(*it1) | (static_cast(*it2) << 8) | (static_cast(*it3) << 16) | (static_cast(*it4) << 24); + if (bom == 0xFFFE0000) + { + return determine_encoding_result{it4++,encoding::u32be}; + } + else if (bom == 0x0000FEFF) + { + return determine_encoding_result{first,encoding::u32le}; + } + else if ((bom & 0xFFFF) == 0xFFFE) + { + return determine_encoding_result{it3,encoding::u16be}; + } + else if ((bom & 0xFFFF) == 0xFEFF) + { + return determine_encoding_result{it3,encoding::u16le}; + } + else if ((bom & 0xFFFFFF) == 0xBFBBEF) + { + return determine_encoding_result{it4,encoding::u8}; + } + else + { + uint32_t pattern = (static_cast(*it1) ? 1 : 0) | (static_cast(*it2) ? 2 : 0) | (static_cast(*it3) ? 4 : 0) | (static_cast(*it4) ? 8 : 0); + switch (pattern) { + case 0x08: + return determine_encoding_result{it1,encoding::u32be}; + case 0x0A: + return determine_encoding_result{it1,encoding::u16be}; + case 0x01: + return determine_encoding_result{it1,encoding::u32le}; + case 0x05: + return determine_encoding_result{it1,encoding::u16le}; + case 0x0F: + return determine_encoding_result{it1,encoding::u8}; + default: + return determine_encoding_result{it1,encoding::undetected}; + } + } + } + } + + // count_codepoints + + template + typename std::enable_if::value || extension_traits::is_char16::value || extension_traits::is_char32::value, std::size_t>::type + count_codepoints(const CharT* data, std::size_t length, + conv_flags flags = conv_flags::strict) noexcept + { + conv_errc ec = conv_errc(); + + std::size_t count = 0; + const CharT* ptr = data; + const CharT* last = data + length; + + for (; ptr < last; ++count) + { + uint32_t cp = 0; + auto r = to_codepoint(ptr, last, cp, flags); + if (r.ec != conv_errc()) + { + ec = r.ec; + break; + } + ptr = r.ptr; + } + return ec == conv_errc() && ptr == last ? count : 0; + } + +} // unicode_traits +} // jsoncons + +#endif + diff --git a/third_party/jsoncons/uri.hpp b/third_party/jsoncons/uri.hpp new file mode 100644 index 0000000000..0b19361572 --- /dev/null +++ b/third_party/jsoncons/uri.hpp @@ -0,0 +1,1151 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_URI_HPP +#define JSONCONS_JSONSCHEMA_URI_HPP + +#include // std::string +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { + + enum class uri_errc + { + success = 0, + invalid_uri = 1, + }; + + + class uri_error_category_impl + : public std::error_category + { + public: + const char* name() const noexcept override + { + return "jsoncons/uri"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case uri_errc::invalid_uri: + return "Invalid URI"; + default: + return "Unknown uri error"; + } + } + }; + + inline + const std::error_category& uri_error_category() + { + static uri_error_category_impl instance; + return instance; + } + + inline + std::error_code make_error_code(uri_errc result) + { + return std::error_code(static_cast(result),uri_error_category()); + } +} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +namespace jsoncons { + + struct uri_fragment_part_t + { + explicit uri_fragment_part_t() = default; + }; + + constexpr uri_fragment_part_t uri_fragment_part{}; + + class uri + { + using part_type = std::pair; + + std::string uri_string_; + part_type scheme_; + part_type userinfo_; + part_type host_; + part_type port_; + part_type path_; + part_type query_; + part_type fragment_; + public: + + uri() + : uri_string_{}, scheme_{0,0},userinfo_{0,0},host_{0,0},port_{0,0},path_{0,0},query_{0,0},fragment_{0,0} + { + } + + uri(const uri& other) + : uri_string_(other.uri_string_), scheme_(other.scheme_), userinfo_(other.userinfo_), host_(other.host_), + port_(other.port_), path_(other.path_), query_(other.query_), fragment_(other.fragment_) + { + } + + uri(uri&& other) noexcept + : uri_string_(std::move(other.uri_string_)), scheme_(other.scheme_), userinfo_(other.userinfo_), host_(other.host_), + port_(other.port_), path_(other.path_), query_(other.query_), fragment_(other.fragment_) + { + } + + uri(const uri& other, uri_fragment_part_t, jsoncons::string_view fragment) + : uri_string_(other.uri_string_), scheme_(other.scheme_), userinfo_(other.userinfo_), host_(other.host_), + port_(other.port_), path_(other.path_), query_(other.query_) + { + uri_string_.erase(query_.second); + if (!fragment.empty()) + { + uri_string_.append("#"); + fragment_.first = uri_string_.length(); + encode_illegal_characters(fragment, uri_string_); + fragment_.second = uri_string_.length(); + } + else + { + fragment_.first = fragment_.second = uri_string_.length(); + } + } + + /*explicit*/ uri(const std::string& uri) + { + std::error_code ec; + *this = parse(uri, ec); + if (ec) + { + JSONCONS_THROW(std::system_error(ec)); + } + } + + uri(jsoncons::string_view scheme, + jsoncons::string_view userinfo, + jsoncons::string_view host, + jsoncons::string_view port, + jsoncons::string_view path, + jsoncons::string_view query, + jsoncons::string_view fragment) + { + if (!scheme.empty()) + { + uri_string_.append(std::string(scheme)); + scheme_.second = uri_string_.length(); + } + if (!userinfo.empty() || !host.empty() || !port.empty()) + { + if (!scheme.empty()) + { + uri_string_.append("://"); + } + + if (!userinfo.empty()) + { + userinfo_.first = uri_string_.length(); + //uri_string_.append(std::string(userinfo)); + encode_userinfo(userinfo, uri_string_); + userinfo_.second = uri_string_.length(); + uri_string_.append("@"); + } + else + { + userinfo_.first = userinfo_.second = uri_string_.length(); + } + + if (!host.empty()) + { + host_.first = uri_string_.length(); + uri_string_.append(std::string(host)); + host_.second = uri_string_.length(); + } + else + { + JSONCONS_THROW(json_runtime_error("uri error.")); + } + + if (!port.empty()) + { + uri_string_.append(":"); + port_.first = uri_string_.length(); + uri_string_.append(std::string(port)); + port_.second = uri_string_.length(); + } + else + { + port_.first = port_.second = uri_string_.length(); + } + } + else + { + userinfo_.first = userinfo_.second = uri_string_.length(); + host_.first = host_.second = uri_string_.length(); + port_.first = port_.second = uri_string_.length(); + if (!scheme.empty()) + { + if (!path.empty() || !query.empty() || !fragment.empty()) + { + uri_string_.append(":"); + } + else + { + JSONCONS_THROW(json_runtime_error("uri error.")); + } + } + } + + if (!path.empty()) + { + // if the URI is not opaque and the path is not already prefixed + // with a '/', add one. + path_.first = uri_string_.length(); + if (!host.empty() && (path.front() != '/')) + { + uri_string_.push_back('/'); + } + //uri_string_.append(std::string(path)); + encode_path(path, uri_string_); + path_.second = uri_string_.length(); + } + else + { + path_.first = path_.second = uri_string_.length(); + } + + if (!query.empty()) + { + uri_string_.append("?"); + query_.first = uri_string_.length(); + encode_illegal_characters(query, uri_string_); + query_.second = uri_string_.length(); + } + else + { + query_.first = query_.second = uri_string_.length(); + } + + if (!fragment.empty()) + { + uri_string_.append("#"); + fragment_.first = uri_string_.length(); + encode_illegal_characters(fragment, uri_string_); + fragment_.second = uri_string_.length(); + } + else + { + fragment_.first = fragment_.second = uri_string_.length(); + } + } + + uri& operator=(const uri& other) noexcept + { + if (&other != this) + { + uri_string_ = other.uri_string_; + scheme_ = other.scheme_; + userinfo_ = other.userinfo_; + host_ = other.host_; + port_ = other.port_; + path_ = other.path_; + query_ = other.query_; + fragment_ = other.fragment_; + } + return *this; + } + + uri& operator=(uri&& other) noexcept + { + if (&other != this) + { + uri_string_ = std::move(other.uri_string_); + scheme_ = other.scheme_; + userinfo_ = other.userinfo_; + host_ = other.host_; + port_ = other.port_; + path_ = other.path_; + query_ = other.query_; + fragment_ = other.fragment_; + } + return *this; + } + + + const std::string& string() const + { + return uri_string_; + } + + bool is_absolute() const noexcept + { + return scheme_.second > scheme_.first; + } + + bool is_opaque() const noexcept + { + return is_absolute() && !encoded_authority().empty(); + } + + uri base() const noexcept + { + return uri{ scheme(), userinfo(), host(), port(), path(), jsoncons::string_view(), jsoncons::string_view()}; + } + + string_view scheme() const noexcept { return string_view(uri_string_.data()+scheme_.first,(scheme_.second-scheme_.first)); } + + string_view encoded_scheme() const noexcept { return string_view(uri_string_.data()+scheme_.first,(scheme_.second-scheme_.first)); } + + std::string userinfo() const + { + return decode_part(encoded_userinfo()); + } + + string_view encoded_userinfo() const noexcept { return string_view(uri_string_.data()+userinfo_.first,(userinfo_.second-userinfo_.first)); } + + string_view host() const noexcept { return string_view(uri_string_.data()+host_.first,(host_.second-host_.first)); } + + string_view encoded_host() const noexcept { return string_view(uri_string_.data()+host_.first,(host_.second-host_.first)); } + + string_view port() const noexcept { return string_view(uri_string_.data()+port_.first,(port_.second-port_.first)); } + + string_view encoded_port() const noexcept { return string_view(uri_string_.data()+port_.first,(port_.second-port_.first)); } + + string_view encoded_authority() const noexcept { return string_view(uri_string_.data()+userinfo_.first,(port_.second-userinfo_.first)); } + + std::string path() const + { + return decode_part(encoded_path()); + } + + string_view encoded_path() const noexcept { return string_view(uri_string_.data()+path_.first,(path_.second-path_.first)); } + + std::string query() const + { + return decode_part(encoded_query()); + } + + string_view encoded_query() const noexcept { return string_view(uri_string_.data()+query_.first,(query_.second-query_.first)); } + + std::string fragment() const + { + return decode_part(encoded_fragment()); + } + + string_view encoded_fragment() const noexcept + { + return string_view(uri_string_.data()+fragment_.first,(fragment_.second-fragment_.first)); + } + + std::string authority() const + { + return decode_part(encoded_authority()); + } + + uri resolve(const uri& base) const + { + // This implementation uses the psuedo-code given in + // http://tools.ietf.org/html/rfc3986#section-5.2.2 + + if (is_absolute() && !is_opaque()) + { + return *this; + } + + if (is_opaque()) + { + return *this; + } + + std::string userinfo, host, port, path, query, fragment; + + if (!encoded_authority().empty()) + { + // g -> http://g + if (!this->encoded_userinfo().empty()) + { + userinfo = std::string(this->encoded_userinfo()); + } + + if (!this->host().empty()) + { + host = std::string(this->host()); + } + + if (!this->port().empty()) + { + port = std::string(this->port()); + } + + if (!this->encoded_path().empty()) + { + path = remove_dot_segments(this->encoded_path()); + } + + if (!this->encoded_query().empty()) + { + query = std::string(this->encoded_query()); + } + } + else + { + if (this->encoded_path().empty()) + { + if (!base.encoded_path().empty()) + { + path = std::string(base.encoded_path()); + } + + if (!this->encoded_query().empty()) + { + query = std::string(this->encoded_query()); + } + else if (!base.encoded_query().empty()) + { + query = std::string(base.encoded_query()); + } + } + else + { + if (this->encoded_path().front() == '/') + { + path = remove_dot_segments(this->encoded_path()); + } + else + { + path = merge_paths(base, *this); + } + + if (!this->encoded_query().empty()) + { + query = std::string(this->encoded_query()); + } + } + + if (!base.encoded_userinfo().empty()) + { + userinfo = std::string(base.encoded_userinfo()); + } + + if (!base.host().empty()) + { + host = std::string(base.host()); + } + + if (!base.port().empty()) + { + port = std::string(base.port()); + } + } + + if (!this->encoded_fragment().empty()) + { + fragment = std::string(this->encoded_fragment()); + } + + return uri(std::string(base.scheme()), userinfo, host, port, path, query, fragment); + } + + int compare(const uri& other) const + { + int result = scheme().compare(other.scheme()); + if (result != 0) return result; + result = encoded_userinfo().compare(other.encoded_userinfo()); + if (result != 0) return result; + result = host().compare(other.host()); + if (result != 0) return result; + result = port().compare(other.port()); + if (result != 0) return result; + result = encoded_path().compare(other.encoded_path()); + if (result != 0) return result; + result = encoded_query().compare(other.encoded_query()); + if (result != 0) return result; + result = encoded_fragment().compare(other.encoded_fragment()); + + return result; + } + + friend bool operator==(const uri& lhs, const uri& rhs) + { + return lhs.compare(rhs) == 0; + } + + friend bool operator!=(const uri& lhs, const uri& rhs) + { + return lhs.compare(rhs) != 0; + } + + friend bool operator<(const uri& lhs, const uri& rhs) + { + return lhs.compare(rhs) < 0; + } + + friend bool operator<=(const uri& lhs, const uri& rhs) + { + return lhs.compare(rhs) <= 0; + } + + friend bool operator>(const uri& lhs, const uri& rhs) + { + return lhs.compare(rhs) > 0; + } + + friend bool operator>=(const uri& lhs, const uri& rhs) + { + return lhs.compare(rhs) >= 0; + } + + static std::string decode_part(const jsoncons::string_view& encoded) + { + std::string decoded; + + std::size_t length = encoded.size(); + for (std::size_t i = 0; i < length;) + { + if (encoded[i] == '%' && (length - i) >= 3) + { + auto hex = encoded.substr(i + 1, 2); + + uint8_t n; + jsoncons::detail::hex_to_integer(hex.data(), hex.size(), n); + decoded.push_back((char)n); + i += 3; + } + else + { + decoded.push_back(encoded[i]); + ++i; + } + } + return decoded; + } + static uri parse(const std::string& s, std::error_code& ec) + { + part_type scheme; + part_type userinfo; + part_type host; + part_type port; + part_type path; + part_type query; + part_type fragment; + + std::size_t start = 0; + + parse_state state = parse_state::expect_scheme; + for (std::size_t i = 0; i < s.size(); ++i) + { + char c = s[i]; + switch (state) + { + case parse_state::expect_scheme: + switch (c) + { + case ':': + scheme = std::make_pair(start,i); + state = parse_state::expect_first_slash; + start = i; + break; + case '#': + userinfo = std::make_pair(start,start); + host = std::make_pair(start,start); + port = std::make_pair(start,start); + path = std::make_pair(start,i); + query = std::make_pair(i,i); + state = parse_state::expect_fragment; + start = i+1; + break; + default: + break; + } + break; + case parse_state::expect_first_slash: + switch (c) + { + case '/': + state = parse_state::expect_second_slash; + break; + default: + start = i; + state = parse_state::expect_path; + break; + } + break; + case parse_state::expect_second_slash: + switch (c) + { + case '/': + state = parse_state::expect_authority; + start = i+1; + break; + default: + break; + } + break; + case parse_state::expect_authority: + switch (c) + { + case '[': + state = parse_state::expect_host_ipv6; + start = i+1; + break; + default: + state = parse_state::expect_userinfo; + start = i; + --i; + break; + } + break; + case parse_state::expect_host_ipv6: + switch (c) + { + case ']': + userinfo = std::make_pair(start,start); + host = std::make_pair(start,i); + port = std::make_pair(i,i); + state = parse_state::expect_path; + start = i+1; + break; + default: + break; + } + break; + case parse_state::expect_userinfo: + switch (c) + { + case '@': + userinfo = std::make_pair(start,i); + state = parse_state::expect_host; + start = i+1; + break; + case ':': + userinfo = std::make_pair(start,start); + host = std::make_pair(start,i); + state = parse_state::expect_port; + start = i+1; + break; + case '/': + userinfo = std::make_pair(start,start); + host = std::make_pair(start,i); + port = std::make_pair(i,i); + state = parse_state::expect_path; + start = i; + break; + default: + break; + } + break; + case parse_state::expect_host: + switch (c) + { + case ':': + host = std::make_pair(start,i); + state = parse_state::expect_port; + start = i+1; + break; + default: + break; + } + break; + case parse_state::expect_port: + switch (c) + { + case '/': + port = std::make_pair(start,i); + state = parse_state::expect_path; + start = i; + break; + default: + break; + } + break; + case parse_state::expect_path: + switch (c) + { + case '?': + path = std::make_pair(start,i); + state = parse_state::expect_query; + start = i+1; + break; + case '#': + path = std::make_pair(start,i); + query = std::make_pair(i,i); + state = parse_state::expect_fragment; + start = i+1; + break; + default: + if (!(is_pchar(c,s.data()+i, s.size() - i) || c == '/')) + { + ec = uri_errc::invalid_uri; + return uri{}; + } + break; + } + break; + case parse_state::expect_query: + switch (c) + { + case '#': + query = std::make_pair(start,i); + state = parse_state::expect_fragment; + start = i+1; + break; + default: + break; + } + break; + case parse_state::expect_fragment: + break; + } + } + switch (state) + { + case parse_state::expect_scheme: + userinfo = std::make_pair(start,start); + host = std::make_pair(start,start); + port = std::make_pair(start,start); + path = std::make_pair(start,s.size()); + query = std::make_pair(s.size(), s.size()); + fragment = std::make_pair(s.size(), s.size()); + break; + case parse_state::expect_userinfo: + userinfo = std::make_pair(start,start); + host = std::make_pair(start,s.size()); + port = std::make_pair(s.size(), s.size()); + path = std::make_pair(s.size(), s.size()); + query = std::make_pair(s.size(), s.size()); + fragment = std::make_pair(s.size(), s.size()); + break; + case parse_state::expect_path: + path = std::make_pair(start,s.size()); + query = std::make_pair(s.size(), s.size()); + fragment = std::make_pair(s.size(), s.size()); + break; + case parse_state::expect_query: + query = std::make_pair(start,s.size()); + fragment = std::make_pair(s.size(), s.size()); + break; + case parse_state::expect_fragment: + fragment = std::make_pair(start,s.size()); + break; + default: + ec = uri_errc::invalid_uri; + break; + } + + return uri(s, scheme, userinfo, host, port, path, query, fragment); + } + + private: + enum class parse_state {expect_scheme, + expect_first_slash, + expect_second_slash, + expect_authority, + expect_host_ipv6, + expect_userinfo, + expect_host, + expect_port, + expect_path, + expect_query, + expect_fragment}; + + uri(const std::string& uri, part_type scheme, part_type userinfo, + part_type host, part_type port, part_type path, + part_type query, part_type fragment) + : uri_string_(uri), scheme_(scheme), userinfo_(userinfo), + host_(host), port_(port), path_(path), + query_(query), fragment_(fragment) + { + } + + static std::string remove_dot_segments(const jsoncons::string_view& input) + { + std::string result = std::string(input); +/* + std::size_t pos = 0; + while (pos < input.size()) + { + if (input.compare(0, 3, "../")) + { + network_boost::erase_head(input, 3); + } else if (network_boost::starts_with(input, "./")) { + network_boost::erase_head(input, 2); + } else if (network_boost::starts_with(input, "/./")) { + network_boost::replace_head(input, 3, "/"); + } else if (input == "/.") { + network_boost::replace_head(input, 2, "/"); + } else if (network_boost::starts_with(input, "/../")) { + network_boost::erase_head(input, 3); + remove_last_segment(result); + } else if (network_boost::starts_with(input, "/..")) { + network_boost::replace_head(input, 3, "/"); + remove_last_segment(result); + } else if (network_boost::algorithm::all(input, [](char ch) { return ch == '.'; })) { + input.clear(); + } + else { + int n = (input.front() == '/')? 1 : 0; + auto slash = network_boost::find_nth(input, "/", n); + result.append(std::begin(input), std::begin(slash)); + input.erase(std::begin(input), std::begin(slash)); + } + } +*/ + return result; + } + + static std::string merge_paths(const uri& base, const uri& relative) + { + std::string result; + + if (base.encoded_path().empty()) + { + result = "/"; + } + else + { + const auto& base_path = base.encoded_path(); + auto last_slash = base_path.rfind('/'); + result.append(std::string(base_path.substr(0,last_slash+1))); + } + if (!relative.encoded_path().empty()) + { + result.append(relative.encoded_path().begin(), relative.encoded_path().end()); + } + return remove_dot_segments(jsoncons::string_view(result)); + } + + static void remove_last_segment(std::string& path) + { + auto last_slash = path.rfind('/'); + if (last_slash != std::string::npos) + { + path.erase(last_slash); + } + } + + static bool is_alpha(char ch) + { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z'); + } + + static bool is_digit(char ch) + { + return (ch >= '0' && ch <= '9'); + } + + static bool is_alphanum(char ch) + { + return is_alpha(ch) || is_digit(ch); + } + + static bool is_unreserved(char ch) + { + switch (ch) + { + case '_': + case '-': + case '!': + case '.': + case '~': + case '\'': + case '(': + case ')': + case '*': + return true; + default: + return is_alphanum(ch); + } + } + + static bool is_punct(char ch) + { + switch (ch) + { + case ',': + case ';': + case ':': + case '$': + case '&': + case '+': + case '=': + return true; + default: + return false; + } + } + + static bool is_reserved(char ch) + { + switch (ch) + { + case '?': + case '/': + case '[': + case ']': + case '@': + return true; + default: + return is_punct(ch); + } + } + + static bool is_hex(char ch) + { + switch(ch) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + return true; + default: + return false; + } + } + + static bool is_pct_encoded(const char* s, std::size_t length) + { + return length < 3 ? false : s[0] == '%' && is_hex(s[1]) && is_hex(s[2]); + } + + // sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "=" + static bool is_sub_delim(char c) + { + switch (c) + { + case '!': + return true; + case '$': + return true; + case '&': + return true; + case '\'': + return true; + case '(': + return true; + case ')': + return true; + case '*': + return true; + case '+': + return true; + case ',': + return true; + case ';': + return true; + case '=': + return true; + default: + return false; + } + } + + public: + + // Any character not in the unreserved, punct or escaped categories, and not equal + // to the slash character ('/') or the commercial-at character ('@'), is quoted. + + static void encode_path(const jsoncons::string_view& sv, std::string& encoded) + { + const std::size_t length1 = sv.size() <= 2 ? 0 : sv.size() - 2; + + std::size_t i = 0; + for (; i < length1; ++i) + { + char ch = sv[i]; + + switch (ch) + { + case '/': + case '@': + encoded.push_back(sv[i]); + break; + default: + { + bool escaped = is_pct_encoded(sv.data()+i,3); + if (!is_unreserved(ch) && !is_punct(ch) && !escaped) + { + encoded.push_back('%'); + if (uint8_t(ch) <= 15) + { + encoded.push_back('0'); + } + jsoncons::detail::integer_to_string_hex((uint8_t)ch, encoded); + } + else if (escaped) + { + encoded.push_back(ch); + encoded.push_back(sv[++i]); + encoded.push_back(sv[++i]); + } + else + { + encoded.push_back(ch); + } + break; + } + } + } + + const std::size_t length2 = sv.size(); + for (; i < length2; ++i) + { + char ch = sv[i]; + + switch (ch) + { + case '/': + case '@': + encoded.push_back(ch); + break; + default: + { + if (!is_unreserved(ch) && !is_punct(ch)) + { + encoded.push_back('%'); + jsoncons::detail::integer_to_string_hex((uint8_t)ch, encoded); + } + else + { + encoded.push_back(ch); + } + break; + } + } + } + } + + + // Any character not in the unreserved, punct, or escaped categories is quoted. + + static void encode_userinfo(const jsoncons::string_view& sv, std::string& encoded) + { + const std::size_t length1 = sv.size() <= 2 ? 0 : sv.size() - 2; + + std::size_t i = 0; + for (; i < length1; ++i) + { + char ch = sv[i]; + + bool escaped = is_pct_encoded(sv.data()+i,3); + if (!is_unreserved(ch) && !is_punct(ch) && !escaped) + { + encoded.push_back('%'); + if (uint8_t(ch) <= 15) + { + encoded.push_back('0'); + } + jsoncons::detail::integer_to_string_hex((uint8_t)ch, encoded); + } + else if (escaped) + { + encoded.push_back(ch); + encoded.push_back(sv[++i]); + encoded.push_back(sv[++i]); + } + else + { + encoded.push_back(ch); + } + } + + const std::size_t length2 = sv.size(); + for (; i < length2; ++i) + { + char ch = sv[i]; + + if (!is_unreserved(ch) && !is_punct(ch)) + { + encoded.push_back('%'); + jsoncons::detail::integer_to_string_hex((uint8_t)ch, encoded); + } + else + { + encoded.push_back(ch); + } + } + } + + // The set of all legal URI characters consists of the unreserved, reserved, escaped characters. + + static void encode_illegal_characters(const jsoncons::string_view& sv, std::string& encoded) + { + const std::size_t length1 = sv.size() <= 2 ? 0 : sv.size() - 2; + + std::size_t i = 0; + for (; i < length1; ++i) + { + char ch = sv[i]; + + bool escaped = is_pct_encoded(sv.data()+i,3); + if (!is_unreserved(ch) && !is_reserved(ch) && !escaped) + { + encoded.push_back('%'); + if (uint8_t(ch) <= 15) + { + encoded.push_back('0'); + } + jsoncons::detail::integer_to_string_hex((uint8_t)ch, encoded); + } + else if (escaped) + { + encoded.push_back(ch); + encoded.push_back(sv[++i]); + encoded.push_back(sv[++i]); + } + else + { + encoded.push_back(ch); + } + } + + const std::size_t length2 = sv.size(); + for (; i < length2; ++i) + { + char ch = sv[i]; + + if (!is_unreserved(ch) && !is_reserved(ch)) + { + encoded.push_back('%'); + jsoncons::detail::integer_to_string_hex((uint8_t)ch, encoded); + } + else + { + encoded.push_back(ch); + } + } + } + + // rel_segment = 1*( unreserved | escaped | ";" | "@" | "&" | "=" | "+" | "$" | "," ) + static bool is_rel_segment(char c, const char* s, std::size_t length) + { + return is_unreserved(c) || is_pct_encoded(s,length) || c == ';' || c == '@' || c == '&' || c == '=' || c == '+' || c == '$' || c == ','; + } + + // userinfo = *( unreserved | escaped | ";" | ":" | "&" | "=" | "+" | "$" | "," ) + + static bool is_userinfo(char c, const char* s, std::size_t length) + { + return is_unreserved(c) || is_pct_encoded(s,length) || c == ';' || c == ':' || c == '&' || c == '=' || c == '+' || c == '$' || c == ','; + } + + static bool is_pchar(char c, const char* s, std::size_t length) + { + return is_unreserved(c) || is_pct_encoded(s,length) || is_sub_delim(c) || c == ':' || c == '@'; + } + }; + +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons/value_converter.hpp b/third_party/jsoncons/value_converter.hpp new file mode 100644 index 0000000000..45b0f752c0 --- /dev/null +++ b/third_party/jsoncons/value_converter.hpp @@ -0,0 +1,340 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CONVERTER_HPP +#define JSONCONS_CONVERTER_HPP + +#include // std::error_code +#include +#include +#include +#include +#include // from_integer +#include + +namespace jsoncons { + + template + class value_converter + { + }; + + template + class value_converter_base + { + public: + using allocator_type = typename std::conditional::value,typename Into::allocator_type, std::allocator>::type; + private: + allocator_type alloc_; + + public: + value_converter_base(const allocator_type& alloc = allocator_type()) + : alloc_(alloc) + { + } + + allocator_type get_allocator() const noexcept + { + return alloc_; + } + }; + + // From any byte sequence, Into string + template + class value_converter::value && !extension_traits::is_string_or_string_view::value && + extension_traits::is_string::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + template + typename std::enable_if::value,Into>::type + convert(const From& value, semantic_tag tag, std::error_code&) + { + Into s(this->get_allocator()); + switch (tag) + { + case semantic_tag::base64: + encode_base64(value.begin(), value.end(), s); + break; + case semantic_tag::base16: + encode_base16(value.begin(), value.end(), s); + break; + default: + encode_base64url(value.begin(), value.end(), s); + break; + } + return s; + } + template + typename std::enable_if::value,Into>::type + convert(const From& value, semantic_tag tag, std::error_code& ec) + { + std::string s; + switch (tag) + { + case semantic_tag::base64: + encode_base64(value.begin(), value.end(), s); + break; + case semantic_tag::base16: + encode_base16(value.begin(), value.end(), s); + break; + default: + encode_base64url(value.begin(), value.end(), s); + break; + } + + Into ws(this->get_allocator()); + auto retval = unicode_traits::convert(s.data(), s.size(), ws); + if (retval.ec != unicode_traits::conv_errc()) + { + ec = conv_errc::not_wide_char; + } + + return ws; + } + }; + + // From byte string, Into byte string + template + class value_converter::value && + !extension_traits::is_string_or_string_view::value && + !extension_traits::is_string_or_string_view::value && + extension_traits::is_back_insertable_byte_container::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + Into convert(const From& value, semantic_tag, std::error_code&) + { + Into s(value.begin(),value.end(),this->get_allocator()); + return s; + } + }; + + // From string or string_view, Into string, same character type + template + class value_converter::value && + extension_traits::is_string::value && + std::is_same::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + Into convert(const From& value, semantic_tag, std::error_code&) + { + return Into(value.begin(),value.end(),this->get_allocator()); + } + }; + + // From string or string_view, Into string, different character type + template + class value_converter::value && + extension_traits::is_string::value && + !std::is_same::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + Into convert(const From& value, semantic_tag, std::error_code& ec) + { + Into ws(this->get_allocator()); + auto retval = unicode_traits::convert(value.data(), value.size(), ws); + if (retval.ec != unicode_traits::conv_errc()) + { + ec = conv_errc::not_wide_char; + } + + return ws; + } + }; + + // From string, Into byte_string + template + class value_converter::value && + !extension_traits::is_string_or_string_view::value && + extension_traits::is_back_insertable_byte_container::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + template + typename std::enable_if::value,Into>::type + convert(const From& value, semantic_tag tag, std::error_code& ec) + { + Into bytes(this->get_allocator()); + switch (tag) + { + case semantic_tag::base16: + { + auto res = decode_base16(value.begin(), value.end(), bytes); + if (res.ec != conv_errc::success) + { + ec = conv_errc::not_byte_string; + } + break; + } + case semantic_tag::base64: + { + decode_base64(value.begin(), value.end(), bytes); + break; + } + case semantic_tag::base64url: + { + decode_base64url(value.begin(), value.end(), bytes); + break; + } + default: + { + ec = conv_errc::not_byte_string; + break; + } + } + return bytes; + } + + template + typename std::enable_if::value,Into>::type + convert(const From& value, semantic_tag tag, std::error_code& ec) + { + Into bytes(this->get_allocator()); + + std::string s(this->get_allocator()); + auto retval = unicode_traits::convert(value.data(), value.size(), s); + if (retval.ec != unicode_traits::conv_errc()) + { + ec = conv_errc::not_wide_char; + } + switch (tag) + { + case semantic_tag::base16: + { + auto res = decode_base16(s.begin(), s.end(), bytes); + if (res.ec != conv_errc::success) + { + ec = conv_errc::not_byte_string; + } + break; + } + case semantic_tag::base64: + { + decode_base64(s.begin(), s.end(), bytes); + break; + } + case semantic_tag::base64url: + { + decode_base64url(s.begin(), s.end(), bytes); + break; + } + default: + { + ec = conv_errc::not_byte_string; + break; + } + } + return bytes; + } + }; + + // From integer, Into string + template + class value_converter::value && + extension_traits::is_string::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + Into convert(From value, semantic_tag, std::error_code&) + { + Into s(this->get_allocator()); + jsoncons::detail::from_integer(value, s); + return s; + } + }; + + // From integer, Into string + template + class value_converter::value && + extension_traits::is_string::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + Into convert(From value, semantic_tag, std::error_code&) + { + Into s(this->get_allocator()); + jsoncons::detail::write_double f{float_chars_format::general,0}; + f(value, s); + return s; + } + }; + + // From half, Into string + template + class value_converter::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + + Into convert(uint16_t value, semantic_tag, std::error_code&) + { + Into s(this->get_allocator()); + jsoncons::detail::write_double f{float_chars_format::general,0}; + double x = binary::decode_half(value); + f(x, s); + return s; + } + }; + + // From bool, Into string + template + class value_converter::value && + extension_traits::is_string::value>::type> : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + using char_type = typename Into::value_type; + + JSONCONS_CPP14_CONSTEXPR + Into convert(From value, semantic_tag, std::error_code&) + { + constexpr const char_type* true_constant = JSONCONS_CSTRING_CONSTANT(char_type,"true"); + constexpr const char_type* false_constant = JSONCONS_CSTRING_CONSTANT(char_type,"false"); + + return value ? Into(true_constant,4) : Into(false_constant,5); + } + }; + + // From null, Into string + template + class value_converter : value_converter_base + { + public: + using allocator_type = typename value_converter_base::allocator_type; + using char_type = typename Into::value_type; + + JSONCONS_CPP14_CONSTEXPR + Into convert(semantic_tag, std::error_code&) + { + constexpr const char_type* null_constant = JSONCONS_CSTRING_CONSTANT(char_type,"null"); + + return Into(null_constant,4); + } + }; + +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons_ext/bson/bson.hpp b/third_party/jsoncons_ext/bson/bson.hpp new file mode 100644 index 0000000000..c08721c95c --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson.hpp @@ -0,0 +1,23 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_HPP +#define JSONCONS_BSON_BSON_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/third_party/jsoncons_ext/bson/bson_cursor.hpp b/third_party/jsoncons_ext/bson/bson_cursor.hpp new file mode 100644 index 0000000000..b86fb92990 --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_cursor.hpp @@ -0,0 +1,263 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_CURSOR_HPP +#define JSONCONS_BSON_BSON_CURSOR_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace bson { + +template > +class basic_bson_cursor : public basic_staj_cursor, private virtual ser_context +{ + using super_type = basic_staj_cursor; +public: + using source_type = Source; + using char_type = char; + using allocator_type = Allocator; +private: + basic_bson_parser parser_; + basic_staj_visitor cursor_visitor_; + bool eof_; + + // Noncopyable and nonmoveable + basic_bson_cursor(const basic_bson_cursor&) = delete; + basic_bson_cursor& operator=(const basic_bson_cursor&) = delete; + +public: + using string_view_type = string_view; + + template + basic_bson_cursor(Sourceable&& source, + const bson_decode_options& options = bson_decode_options(), + const Allocator& alloc = Allocator()) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + eof_(false) + { + if (!done()) + { + next(); + } + } + + // Constructors that set parse error codes + + template + basic_bson_cursor(Sourceable&& source, + std::error_code& ec) + : basic_bson_cursor(std::allocator_arg, Allocator(), + std::forward(source), + bson_decode_options(), + ec) + { + } + + template + basic_bson_cursor(Sourceable&& source, + const bson_decode_options& options, + std::error_code& ec) + : basic_bson_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + ec) + { + } + + template + basic_bson_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const bson_decode_options& options, + std::error_code& ec) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + eof_(false) + { + if (!done()) + { + next(ec); + } + } + + void reset() + { + parser_.reset(); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + template + void reset(Sourceable&& source) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + void reset(std::error_code& ec) + { + parser_.reset(); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + template + void reset(Sourceable&& source, std::error_code& ec) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + bool done() const override + { + return parser_.done(); + } + + void array_expected(std::error_code& ec) override + { + if (cursor_visitor_.event().event_type() == staj_event_type::begin_object) + { + parser_.array_expected(cursor_visitor_, ec); + } + else + { + super_type::array_expected(ec); + } + } + + const staj_event& current() const override + { + return cursor_visitor_.event(); + } + + void read_to(basic_json_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_json_visitor& visitor, + std::error_code& ec) override + { + if (cursor_visitor_.event().send_json_event(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + const ser_context& context() const override + { + return *this; + } + + bool eof() const + { + return eof_; + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + staj_filter_view operator|(basic_bson_cursor& cursor, + std::function pred) + { + return staj_filter_view(cursor, pred); + } + +private: + static bool accept_all(const staj_event&, const ser_context&) + { + return true; + } + + void read_next(std::error_code& ec) + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(cursor_visitor_, ec); + if (ec) return; + } + } + + void read_next(basic_json_visitor& visitor, std::error_code& ec) + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(visitor, ec); + if (ec) return; + } + } +}; + +using bson_stream_cursor = basic_bson_cursor; +using bson_bytes_cursor = basic_bson_cursor; + +} // namespace bson +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons_ext/bson/bson_decimal128.hpp b/third_party/jsoncons_ext/bson/bson_decimal128.hpp new file mode 100644 index 0000000000..b487a04fd4 --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_decimal128.hpp @@ -0,0 +1,865 @@ +#ifndef JSONCONS_BSON_BSON_DECIMAL128_HPP +#define JSONCONS_BSON_BSON_DECIMAL128_HPP + +/* + * Implements decimal128_to_chars and decimal128_from_chars + * + * Based on the libjson functions bson_decimal128_to_string + * and bson_decimal128_from_string_w_len, available at + * https://github.com/mongodb/mongo-c-driver/blob/master/src/libbson/src/bson/bson-decimal128.h + * and https://github.com/mongodb/mongo-c-driver/blob/master/src/libbson/src/bson/bson-decimal128.c + * +*/ + +/* + * Copyright 2015 MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace bson { + + struct decimal128_to_chars_result + { + char* ptr; + std::errc ec; + }; + + struct decimal128_from_chars_result + { + const char* ptr; + std::errc ec; + }; + +/** + * BSON_DECIMAL128_STRING: + * + * The length of a decimal128 string (with null terminator). + * + * 1 for the sign + * 35 for digits and radix + * 2 for exponent indicator and sign + * 4 for exponent digits + */ +#define BSON_DECIMAL128_STRING 43 + + struct TP1 + { + uint64_t low; + uint64_t high; + + constexpr TP1() : low(0), high(0) {} + constexpr TP1(uint64_t hi, uint64_t lo) : low(lo), high(hi) {} + }; + struct TP2 + { + uint64_t high; + uint64_t low; + + constexpr TP2() : high(0), low(0) {} + constexpr TP2(uint64_t hi, uint64_t lo) : high(hi), low(lo) {} + }; + + typedef std::conditional< + jsoncons::endian::native == jsoncons::endian::little, + TP1, + TP2 + >::type decimal128_t; + + inline + bool operator==(const decimal128_t& lhs, const decimal128_t& rhs) + { + return lhs.high == rhs.high && lhs.low == rhs.low; + } + + inline + bool operator!=(const decimal128_t& lhs, const decimal128_t& rhs) + { + return !(lhs == rhs); + } + + struct decimal128_limits + { + // The length of a decimal128 string (without null terminator). + // + // 1 for the sign + // 35 for digits and radix + // 2 for exponent indicator and sign + // 4 for exponent digits + static constexpr int buf_size = 42; + static constexpr int exponent_max = 6111; + static constexpr int exponent_min = -6176; + static constexpr int exponent_bias = 6176; + static constexpr int max_digits = 34; + + static constexpr decimal128_t nan() {return decimal128_t(0x7c00000000000000ull, 0);} + static constexpr decimal128_t infinity() {return decimal128_t(0x7800000000000000ull, 0);} + static constexpr decimal128_t neg_infinity() {return decimal128_t(0x7800000000000000ull + 0x8000000000000000ull, 0);} + }; + + inline + bool is_nan(decimal128_t dec) { return dec == decimal128_limits::nan(); } + + inline + bool is_inf(decimal128_t dec) { return dec == decimal128_limits::infinity(); } + + inline + bool is_neg_inf(decimal128_t dec) { return dec == decimal128_limits::neg_infinity(); } + + /** + * bson_uint128_t: + * + * This struct represents a 128 bit integer. + */ + typedef struct { + uint32_t parts[4]; /* 32-bit words stored high to low. */ + } bson_uint128_t; + + typedef struct { + uint64_t high, low; + } bson_uint128_6464_t; + + namespace detail { + + /** + *------------------------------------------------------------------------------ + * + * bson_uint128_divide1B -- + * + * This function divides a #bson_uint128_t by 1000000000 (1 billion) and + * computes the quotient and remainder. + * + * The remainder will contain 9 decimal digits for conversion to string. + * + * @value The #bson_uint128_t operand. + * @quotient A pointer to store the #bson_uint128_t quotient. + * @rem A pointer to store the #uint64_t remainder. + * + * Returns: + * The quotient at @quotient and the remainder at @rem. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------------ + */ + + inline + void bson_uint128_divide1B (bson_uint128_t value, /* IN */ + bson_uint128_t *quotient, /* OUT */ + uint32_t *rem) /* OUT */ + { + const uint32_t DIVISOR = 1000 * 1000 * 1000; + uint64_t _rem = 0; + int i = 0; + + if (!value.parts[0] && !value.parts[1] && !value.parts[2] && + !value.parts[3]) { + *quotient = value; + *rem = 0; + return; + } + + for (i = 0; i <= 3; i++) { + _rem <<= 32; /* Adjust remainder to match value of next dividend */ + _rem += value.parts[i]; /* Add the divided to _rem */ + value.parts[i] = (uint32_t) (_rem / DIVISOR); + _rem %= DIVISOR; /* Store the remainder */ + } + + *quotient = value; + *rem = (uint32_t) _rem; + } + + /** + *------------------------------------------------------------------------- + * + * mul64x64 -- + * + * This function multiplies two &uint64_t into a &bson_uint128_6464_t. + * + * Returns: + * The product of @left and @right. + * + * Side Effects: + * None. + * + *------------------------------------------------------------------------- + */ + + inline + void mul_64x64 (uint64_t left, /* IN */ + uint64_t right, /* IN */ + bson_uint128_6464_t *product) /* OUT */ + { + uint64_t left_high, left_low, right_high, right_low, product_high, + product_mid, product_mid2, product_low; + bson_uint128_6464_t rt = {0,0}; + + if (!left && !right) { + *product = rt; + return; + } + + left_high = left >> 32; + left_low = (uint32_t) left; + right_high = right >> 32; + right_low = (uint32_t) right; + + product_high = left_high * right_high; + product_mid = left_high * right_low; + product_mid2 = left_low * right_high; + product_low = left_low * right_low; + + product_high += product_mid >> 32; + product_mid = (uint32_t) product_mid + product_mid2 + (product_low >> 32); + + product_high = product_high + (product_mid >> 32); + product_low = (product_mid << 32) + (uint32_t) product_low; + + rt.high = product_high; + rt.low = product_low; + *product = rt; + } + + /** + *------------------------------------------------------------------------------ + * + * dec128_tolower -- + * + * This function converts the ASCII character @c to lowercase. It is locale + * insensitive (unlike the stdlib tolower). + * + * Returns: + * The lowercased character. + */ + + inline + char dec128_tolower (char c) + { + if (isupper (c)) { + c += 32; + } + + return c; + } + + /** + *------------------------------------------------------------------------------ + * + * dec128_istreq -- + * + * This function compares the null-terminated *ASCII* strings @a and @b + * for case-insensitive equality. + * + * Returns: + * true if the strings are equal, false otherwise. + */ + + inline + bool dec128_istreq (const char* a, const char* lasta, + const char* b, const char* lastb) + { + while (!(a == lasta && b == lastb)) + { + // strings are different lengths + if (a == lasta || b == lastb) + { + return false; + } + + if (dec128_tolower (*a) != dec128_tolower (*b)) { + return false; + } + + a++; + b++; + } + + return true; + } + + } // namespace detail + + + /** + *------------------------------------------------------------------------------ + * + * decimal128_to_chars -- + * + * This function converts a BID formatted decimal128 value to string, + * accepting a &decimal128_t as @dec. The string is stored at @str. + * + * @dec : The BID formatted decimal to convert. + * @str : The output decimal128 string. At least %BSON_DECIMAL128_STRING + *characters. + * + * Returns: + * None. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------------ + */ + + inline + decimal128_to_chars_result decimal128_to_chars(char* first, char* last, const decimal128_t& dec) + { + const std::string bson_decimal128_inf = "Infinity"; + const std::string bson_decimal128_nan = "NaN"; + + const uint32_t combination_mask = 0x1f; /* Extract least significant 5 bits */ + const uint32_t exponent_mask = 0x3fff; /* Extract least significant 14 bits */ + const uint32_t combination_infinity = 30; /* Value of combination field for Inf */ + const uint32_t combination_nan = 31; /* Value of combination field for NaN */ + const uint32_t exponent_bias = 6176; /* decimal128 exponent bias */ + + char* str_out = first; /* output pointer in string */ + char significand_str[35]; /* decoded significand digits */ + + /* Note: bits in this routine are referred to starting at 0, */ + /* from the sign bit, towards the coefficient. */ + uint32_t high; /* bits 0 - 31 */ + uint32_t midh; /* bits 32 - 63 */ + uint32_t midl; /* bits 64 - 95 */ + uint32_t low; /* bits 96 - 127 */ + uint32_t combination; /* bits 1 - 5 */ + uint32_t biased_exponent; /* decoded biased exponent (14 bits) */ + uint32_t significand_digits = 0; /* the number of significand digits */ + uint32_t significand[36] = {0}; /* the base-10 digits in the significand */ + uint32_t *significand_read = significand; /* read pointer into significand */ + int32_t exponent; /* unbiased exponent */ + int32_t scientific_exponent; /* the exponent if scientific notation is + * used */ + bool is_zero = false; /* true if the number is zero */ + + uint8_t significand_msb; /* the most signifcant significand bits (50-46) */ + bson_uint128_t + significand128; /* temporary storage for significand decoding */ + + memset (significand_str, 0, sizeof (significand_str)); + + if ((int64_t) dec.high < 0) { /* negative */ + *(str_out++) = '-'; + } + + low = (uint32_t) dec.low, midl = (uint32_t) (dec.low >> 32), + midh = (uint32_t) dec.high, high = (uint32_t) (dec.high >> 32); + + /* Decode combination field and exponent */ + combination = (high >> 26) & combination_mask; + + if (JSONCONS_UNLIKELY ((combination >> 3) == 3)) { + /* Check for 'special' values */ + if (combination == combination_infinity) { /* Infinity */ + if (last-str_out >= static_cast(bson_decimal128_inf.size())) + { + std::memcpy(str_out, bson_decimal128_inf.data(), bson_decimal128_inf.size()); + str_out += bson_decimal128_inf.size(); + } + *str_out = 0; + //strcpy_s (str_out, last-str_out, bson_decimal128_inf.c_str()); + return decimal128_to_chars_result{str_out, std::errc()}; + } else if (combination == combination_nan) { /* NaN */ + /* first, not str_out, to erase the sign */ + str_out = first; + if (last-str_out >= static_cast(bson_decimal128_nan.size())) + { + std::memcpy(str_out, bson_decimal128_nan.data(), bson_decimal128_nan.size()); + str_out += bson_decimal128_nan.size(); + } + *str_out = 0; + //strcpy_s (first, last-first, bson_decimal128_nan.c_str()); + /* we don't care about the NaN payload. */ + return decimal128_to_chars_result{str_out, std::errc()}; + } else { + biased_exponent = (high >> 15) & exponent_mask; + significand_msb = 0x8 + ((high >> 14) & 0x1); + } + } else { + significand_msb = (high >> 14) & 0x7; + biased_exponent = (high >> 17) & exponent_mask; + } + + exponent = biased_exponent - exponent_bias; + /* Create string of significand digits */ + + /* Convert the 114-bit binary number represented by */ + /* (high, midh, midl, low) to at most 34 decimal */ + /* digits through modulo and division. */ + significand128.parts[0] = (high & 0x3fff) + ((significand_msb & 0xf) << 14); + significand128.parts[1] = midh; + significand128.parts[2] = midl; + significand128.parts[3] = low; + + if (significand128.parts[0] == 0 && significand128.parts[1] == 0 && + significand128.parts[2] == 0 && significand128.parts[3] == 0) { + is_zero = true; + } else if (significand128.parts[0] >= (1 << 17)) { + /* The significand is non-canonical or zero. + * In order to preserve compatibility with the densely packed decimal + * format, the maximum value for the significand of decimal128 is + * 1e34 - 1. If the value is greater than 1e34 - 1, the IEEE 754 + * standard dictates that the significand is interpreted as zero. + */ + is_zero = true; + } else { + for (int k = 3; k >= 0; k--) { + uint32_t least_digits = 0; + detail::bson_uint128_divide1B ( + significand128, &significand128, &least_digits); + + /* We now have the 9 least significant digits (in base 2). */ + /* Convert and output to string. */ + if (!least_digits) { + continue; + } + + for (int j = 8; j >= 0; j--) { + significand[k * 9 + j] = least_digits % 10; + least_digits /= 10; + } + } + } + + /* Output format options: */ + /* Scientific - [-]d.dddE(+/-)dd or [-]dE(+/-)dd */ + /* Regular - ddd.ddd */ + + if (is_zero) { + significand_digits = 1; + *significand_read = 0; + } else { + significand_digits = 36; + while (!(*significand_read)) { + significand_digits--; + significand_read++; + } + } + + scientific_exponent = significand_digits - 1 + exponent; + + /* The scientific exponent checks are dictated by the string conversion + * specification and are somewhat arbitrary cutoffs. + * + * We must check exponent > 0, because if this is the case, the number + * has trailing zeros. However, we *cannot* output these trailing zeros, + * because doing so would change the precision of the value, and would + * change stored data if the string converted number is round tripped. + */ + if (scientific_exponent < -6 || exponent > 0) { + /* Scientific format */ + *(str_out++) = char(*(significand_read++)) + '0'; + significand_digits--; + + if (significand_digits) { + *(str_out++) = '.'; + } + + for (std::size_t i = 0; i < significand_digits && (str_out - first) < 36; i++) { + *(str_out++) = char(*(significand_read++)) + '0'; + } + /* Exponent */ + *(str_out++) = 'E'; + + std::string s; + if (scientific_exponent >= 0) { + s.push_back('+'); + } + jsoncons::detail::from_integer(scientific_exponent, s); + if (str_out + s.size() < last) + { + std::memcpy(str_out, s.data(), s.size()); + } + else + { + return decimal128_to_chars_result{str_out, std::errc::value_too_large}; + } + str_out += s.size(); + } else { + /* Regular format with no decimal place */ + if (exponent >= 0) { + for (std::size_t i = 0; i < significand_digits && (str_out - first) < 36; i++) { + *(str_out++) = char(*(significand_read++)) + '0'; + } + } else { + int32_t radix_position = significand_digits + exponent; + + if (radix_position > 0) { /* non-zero digits before radix */ + for (int32_t i = 0; + i < radix_position && (str_out < last); + i++) { + *(str_out++) = char(*(significand_read++)) + '0'; + } + } else { /* leading zero before radix point */ + *(str_out++) = '0'; + } + + *(str_out++) = '.'; + while (radix_position++ < 0) { /* add leading zeros after radix */ + *(str_out++) = '0'; + } + + for (std::size_t i = 0; + (i < significand_digits - (std::max) (radix_position - 1, 0)) && + (str_out < last); + i++) { + *(str_out++) = char(*(significand_read++)) + '0'; + } + } + } + return decimal128_to_chars_result{str_out, std::errc()}; + } + + + + /** + *------------------------------------------------------------------------------ + * + * bson_decimal128_from_string_w_len -- + * + * This function converts @string in the format [+-]ddd[.]ddd[E][+-]dddd to + * decimal128. Out of range values are converted to +/-Infinity. Invalid + * strings are converted to NaN. @len is the length of the string, or -1 + * meaning the string is null-terminated. + * + * If more digits are provided than the available precision allows, + * round to the nearest expressable decimal128 with ties going to even will + * occur. + * + * Note: @string must be ASCII only! + * + * Returns: + * true on success, or false on failure. @dec will be NaN if @str was invalid + * The &decimal128_t converted from @string at @dec. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------------ + */ + + inline + decimal128_from_chars_result decimal128_from_chars(const char* first, const char* last, decimal128_t& dec) + { + const string_view inf_str = "inf"; + const string_view infinity_str = "infinity"; + const string_view nan_str = "nan"; + + ptrdiff_t len = last - first; + + bson_uint128_6464_t significand = {0,0}; + + const char* str_read = first; /* Read pointer for consuming str. */ + + /* Parsing state tracking */ + bool is_negative = false; + bool saw_radix = false; + bool includes_sign = false; /* True if the input first contains a sign. */ + bool found_nonzero = false; + + size_t significant_digits = 0; /* Total number of significant digits + * (no leading or trailing zero) */ + size_t ndigits_read = 0; /* Total number of significand digits read */ + size_t ndigits = 0; /* Total number of digits (no leading zeros) */ + size_t radix_position = 0; /* The number of the digits after radix */ + size_t first_nonzero = 0; /* The index of the first non-zero in *str* */ + + uint16_t digits[decimal128_limits::max_digits] = {0}; + uint16_t ndigits_stored = 0; /* The number of digits in digits */ + uint16_t *digits_insert = digits; /* Insertion pointer for digits */ + size_t first_digit = 0; /* The index of the first non-zero digit */ + size_t last_digit = 0; /* The index of the last digit */ + + int32_t exponent = 0; + uint64_t significand_high = 0; /* The high 17 digits of the significand */ + uint64_t significand_low = 0; /* The low 17 digits of the significand */ + uint16_t biased_exponent = 0; /* The biased exponent */ + + dec.high = 0; + dec.low = 0; + + if (*str_read == '+' || *str_read == '-') { + is_negative = *(str_read++) == '-'; + includes_sign = true; + } + + /* Check for Infinity or NaN */ + if (!isdigit (*str_read) && *str_read != '.') { + if (detail::dec128_istreq (str_read, last, inf_str.data(), inf_str.data()+inf_str.length()) || + detail::dec128_istreq (str_read, last, infinity_str.data(), infinity_str.data()+infinity_str.length())) + { + dec = is_negative ? decimal128_limits::neg_infinity() : decimal128_limits::infinity(); + return decimal128_from_chars_result{str_read,std::errc()}; + } else if (detail::dec128_istreq (str_read, last, nan_str.data(), nan_str.data()+nan_str.length())) { + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc()}; + } + + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + /* Read digits */ + while (((isdigit (*str_read) || *str_read == '.')) && + (len == -1 || str_read < first + len)) { + if (*str_read == '.') { + if (saw_radix) { + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + saw_radix = true; + str_read++; + continue; + } + + if (ndigits_stored < 34) { + if (*str_read != '0' || found_nonzero) { + if (!found_nonzero) { + first_nonzero = ndigits_read; + } + + found_nonzero = true; + *(digits_insert++) = *(str_read) - '0'; /* Only store 34 digits */ + ndigits_stored++; + } + } + + if (found_nonzero) { + ndigits++; + } + + if (saw_radix) { + radix_position++; + } + + ndigits_read++; + str_read++; + } + + if (saw_radix && !ndigits_read) { + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + /* Read exponent if exists */ + if (*str_read == 'e' || *str_read == 'E') { + ++str_read; + if (*str_read == '+') { + ++str_read; + } + auto result = jsoncons::detail::to_integer(str_read, last - str_read, exponent); + if (result.ec != jsoncons::detail::to_integer_errc()) + { + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + str_read = result.ptr; + } + + if ((len == -1 || str_read < first + len) && *str_read) { + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + /* Done reading input. */ + /* Find first non-zero digit in digits */ + first_digit = 0; + + if (!ndigits_stored) { /* value is zero */ + first_digit = 0; + last_digit = 0; + digits[0] = 0; + ndigits = 1; + ndigits_stored = 1; + significant_digits = 0; + } else { + last_digit = ndigits_stored - 1; + significant_digits = ndigits; + /* Mark trailing zeros as non-significant */ + while (first[first_nonzero + significant_digits - 1 + includes_sign + + saw_radix] == '0') { + significant_digits--; + } + } + + + /* Normalization of exponent */ + /* Correct exponent based on radix position, and shift significand as needed + */ + /* to represent user input */ + + /* Overflow prevention */ + if (exponent <= static_cast(radix_position) && static_cast(radix_position) - exponent > (1 << 14)) { + exponent = decimal128_limits::exponent_min; + } else { + exponent -= static_cast(radix_position); + } + + /* Attempt to normalize the exponent */ + while (exponent > decimal128_limits::exponent_max) { + /* Shift exponent to significand and decrease */ + last_digit++; + + if (last_digit - first_digit > decimal128_limits::max_digits) { + /* The exponent is too great to shift into the significand. */ + if (significant_digits == 0) { + /* Value is zero, we are allowed to clamp the exponent. */ + exponent = decimal128_limits::exponent_max; + break; + } + + /* Overflow is not permitted, error. */ + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + exponent--; + } + + while (exponent < decimal128_limits::exponent_min || ndigits_stored < ndigits) { + /* Shift last digit */ + if (last_digit == 0) { + /* underflow is not allowed, but zero clamping is */ + if (significant_digits == 0) { + exponent = decimal128_limits::exponent_min; + break; + } + + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + if (ndigits_stored < ndigits) { + if (first[ndigits - 1 + includes_sign + saw_radix] - '0' != 0 && + significant_digits != 0) { + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + ndigits--; /* adjust to match digits not stored */ + } else { + if (digits[last_digit] != 0) { + /* Inexact rounding is not allowed. */ + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + + + last_digit--; /* adjust to round */ + } + + if (exponent < decimal128_limits::exponent_max) { + exponent++; + } else { + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + } + + /* Round */ + /* We've normalized the exponent, but might still need to round. */ + if (last_digit - first_digit + 1 < significant_digits) { + uint8_t round_digit; + + /* There are non-zero digits after last_digit that need rounding. */ + /* We round to nearest, ties to even */ + round_digit = + first[first_nonzero + last_digit + includes_sign + saw_radix + 1] - + '0'; + + if (round_digit != 0) { + /* Inexact (non-zero) rounding is not allowed */ + dec = decimal128_limits::nan(); + return decimal128_from_chars_result{str_read,std::errc::invalid_argument}; + } + } + + /* Encode significand */ + significand_high = 0, /* The high 17 digits of the significand */ + significand_low = 0; /* The low 17 digits of the significand */ + + if (significant_digits == 0) { /* read a zero */ + significand_high = 0; + significand_low = 0; + } else if (last_digit - first_digit < 17) { + size_t d_idx = first_digit; + significand_low = digits[d_idx++]; + + for (; d_idx <= last_digit; d_idx++) { + significand_low *= 10; + significand_low += digits[d_idx]; + significand_high = 0; + } + } else { + size_t d_idx = first_digit; + significand_high = digits[d_idx++]; + + for (; d_idx <= last_digit - 17; d_idx++) { + significand_high *= 10; + significand_high += digits[d_idx]; + } + + significand_low = digits[d_idx++]; + + for (; d_idx <= last_digit; d_idx++) { + significand_low *= 10; + significand_low += digits[d_idx]; + } + } + + detail::mul_64x64 (significand_high, 100000000000000000ull, &significand); + significand.low += significand_low; + + if (significand.low < significand_low) { + significand.high += 1; + } + + + biased_exponent = static_cast(exponent + static_cast(decimal128_limits::exponent_bias)); + + /* Encode combination, exponent, and significand. */ + if ((significand.high >> 49) & 1) { + /* Encode '11' into bits 1 to 3 */ + dec.high |= (0x3ull << 61); + dec.high |= (biased_exponent & 0x3fffull) << 47; + dec.high |= significand.high & 0x7fffffffffffull; + } else { + dec.high |= (biased_exponent & 0x3fffull) << 49; + dec.high |= significand.high & 0x1ffffffffffffull; + } + + dec.low = significand.low; + + /* Encode sign */ + if (is_negative) { + dec.high |= 0x8000000000000000ull; + } + + return decimal128_from_chars_result{str_read,std::errc()}; + } + +} // namespace bson +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/bson/bson_decimal128.hpp.bak b/third_party/jsoncons_ext/bson/bson_decimal128.hpp.bak new file mode 100644 index 0000000000..8e27ee199c --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_decimal128.hpp.bak @@ -0,0 +1,816 @@ +#ifndef JSONCONS_BSON_BSON_DECIMAL128_HPP +#define JSONCONS_BSON_BSON_DECIMAL128_HPP + +/* + * Copyright 2015 MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +namespace jsoncons { namespace bson { + + struct decimal128_to_chars_result + { + char* ptr; + std::errc ec; + }; + + struct decimal128_from_chars_result + { + const char* ptr; + std::errc ec; + }; + +/** + * BSON_DECIMAL128_STRING: + * + * The length of a decimal128 string (with null terminator). + * + * 1 for the sign + * 35 for digits and radix + * 2 for exponent indicator and sign + * 4 for exponent digits + */ +#define BSON_DECIMAL128_STRING 43 +#define BSON_DECIMAL128_INF "Infinity" +#define BSON_DECIMAL128_NAN "NaN" + + struct TP1 + { + uint64_t low; + uint64_t high; + + constexpr TP1() : low(0), high(0) {} + constexpr TP1(uint64_t hi, uint64_t lo) : low(lo), high(hi) {} + }; + struct TP2 + { + uint64_t high; + uint64_t low; + + constexpr TP2() : high(0), low(0) {} + constexpr TP2(uint64_t hi, uint64_t lo) : high(hi), low(lo) {} + }; + + typedef typename std::conditional< + jsoncons::endian::native == jsoncons::endian::little, + TP1, + TP2 + >::type decimal128_t; + + inline + bool operator==(const decimal128_t& lhs, const decimal128_t& rhs) + { + return lhs.high == rhs.high && lhs.low == rhs.low; + } + + inline + bool operator!=(const decimal128_t& lhs, const decimal128_t& rhs) + { + return !(lhs == rhs); + } + + struct decimal128_limits + { + // The length of a decimal128 string (without null terminator). + // + // 1 for the sign + // 35 for digits and radix + // 2 for exponent indicator and sign + // 4 for exponent digits + static constexpr int recommended_buffer_size = 42; + static constexpr decimal128_t nan = decimal128_t(0x7c00000000000000ull, 0); + static constexpr decimal128_t infinity = decimal128_t(0x7800000000000000ull, 0); + static constexpr decimal128_t neg_infinity = decimal128_t(0x7800000000000000ull + 0x8000000000000000ull, 0); + static constexpr int exponent_max = 6111; + static constexpr int exponent_min = -6176; + static constexpr int exponent_bias = 6176; + static constexpr int max_digits = 34; + }; + + /** + * bson_uint128_t: + * + * This struct represents a 128 bit integer. + */ + typedef struct { + uint32_t parts[4]; /* 32-bit words stored high to low. */ + } bson_uint128_t; + + typedef struct { + uint64_t high, low; + } bson_uint128_6464_t; + + namespace detail { + + /** + *------------------------------------------------------------------------------ + * + * bson_uint128_divide1B -- + * + * This function divides a #bson_uint128_t by 1000000000 (1 billion) and + * computes the quotient and remainder. + * + * The remainder will contain 9 decimal digits for conversion to string. + * + * @value The #bson_uint128_t operand. + * @quotient A pointer to store the #bson_uint128_t quotient. + * @rem A pointer to store the #uint64_t remainder. + * + * Returns: + * The quotient at @quotient and the remainder at @rem. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------------ + */ + + inline + void bson_uint128_divide1B (bson_uint128_t value, /* IN */ + bson_uint128_t *quotient, /* OUT */ + uint32_t *rem) /* OUT */ + { + const uint32_t DIVISOR = 1000 * 1000 * 1000; + uint64_t _rem = 0; + int i = 0; + + if (!value.parts[0] && !value.parts[1] && !value.parts[2] && + !value.parts[3]) { + *quotient = value; + *rem = 0; + return; + } + + for (i = 0; i <= 3; i++) { + _rem <<= 32; /* Adjust remainder to match value of next dividend */ + _rem += value.parts[i]; /* Add the divided to _rem */ + value.parts[i] = (uint32_t) (_rem / DIVISOR); + _rem %= DIVISOR; /* Store the remainder */ + } + + *quotient = value; + *rem = (uint32_t) _rem; + } + + /** + *------------------------------------------------------------------------- + * + * mul64x64 -- + * + * This function multiplies two &uint64_t into a &bson_uint128_6464_t. + * + * Returns: + * The product of @left and @right. + * + * Side Effects: + * None. + * + *------------------------------------------------------------------------- + */ + + inline + void mul_64x64 (uint64_t left, /* IN */ + uint64_t right, /* IN */ + bson_uint128_6464_t *product) /* OUT */ + { + uint64_t left_high, left_low, right_high, right_low, product_high, + product_mid, product_mid2, product_low; + bson_uint128_6464_t rt = {0}; + + if (!left && !right) { + *product = rt; + return; + } + + left_high = left >> 32; + left_low = (uint32_t) left; + right_high = right >> 32; + right_low = (uint32_t) right; + + product_high = left_high * right_high; + product_mid = left_high * right_low; + product_mid2 = left_low * right_high; + product_low = left_low * right_low; + + product_high += product_mid >> 32; + product_mid = (uint32_t) product_mid + product_mid2 + (product_low >> 32); + + product_high = product_high + (product_mid >> 32); + product_low = (product_mid << 32) + (uint32_t) product_low; + + rt.high = product_high; + rt.low = product_low; + *product = rt; + } + + /** + *------------------------------------------------------------------------------ + * + * dec128_tolower -- + * + * This function converts the ASCII character @c to lowercase. It is locale + * insensitive (unlike the stdlib tolower). + * + * Returns: + * The lowercased character. + */ + + inline + char dec128_tolower (char c) + { + if (isupper (c)) { + c += 32; + } + + return c; + } + + /** + *------------------------------------------------------------------------------ + * + * dec128_istreq -- + * + * This function compares the null-terminated *ASCII* strings @a and @b + * for case-insensitive equality. + * + * Returns: + * true if the strings are equal, false otherwise. + */ + + inline + bool dec128_istreq (const char* a, /* IN */ + const char* b /* IN */) + { + while (*a != '\0' || *b != '\0') { + /* strings are different lengths. */ + if (*a == '\0' || *b == '\0') { + return false; + } + + if (dec128_tolower (*a) != dec128_tolower (*b)) { + return false; + } + + a++; + b++; + } + + return true; + } + + } // namespace detail + + + /** + *------------------------------------------------------------------------------ + * + * decimal128_to_chars -- + * + * This function converts a BID formatted decimal128 value to string, + * accepting a &decimal128_t as @dec. The string is stored at @str. + * + * @dec : The BID formatted decimal to convert. + * @str : The output decimal128 string. At least %BSON_DECIMAL128_STRING + *characters. + * + * Returns: + * None. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------------ + */ + + inline + void decimal128_to_chars(char* first, char* last, const decimal128_t& dec) + { + uint32_t COMBINATION_MASK = 0x1f; /* Extract least significant 5 bits */ + uint32_t EXPONENT_MASK = 0x3fff; /* Extract least significant 14 bits */ + uint32_t COMBINATION_INFINITY = 30; /* Value of combination field for Inf */ + uint32_t COMBINATION_NAN = 31; /* Value of combination field for NaN */ + uint32_t EXPONENT_BIAS = 6176; /* decimal128 exponent bias */ + + char* str_out = first; /* output pointer in string */ + char significand_str[35]; /* decoded significand digits */ + + + /* Note: bits in this routine are referred to starting at 0, */ + /* from the sign bit, towards the coefficient. */ + uint32_t high; /* bits 0 - 31 */ + uint32_t midh; /* bits 32 - 63 */ + uint32_t midl; /* bits 64 - 95 */ + uint32_t low; /* bits 96 - 127 */ + uint32_t combination; /* bits 1 - 5 */ + uint32_t biased_exponent; /* decoded biased exponent (14 bits) */ + uint32_t significand_digits = 0; /* the number of significand digits */ + uint32_t significand[36] = {0}; /* the base-10 digits in the significand */ + uint32_t *significand_read = significand; /* read pointer into significand */ + int32_t exponent; /* unbiased exponent */ + int32_t scientific_exponent; /* the exponent if scientific notation is + * used */ + bool is_zero = false; /* true if the number is zero */ + + uint8_t significand_msb; /* the most signifcant significand bits (50-46) */ + bson_uint128_t + significand128; /* temporary storage for significand decoding */ + size_t i; /* indexing variables */ + int j, k; + + memset (significand_str, 0, sizeof (significand_str)); + + if ((int64_t) dec.high < 0) { /* negative */ + *(str_out++) = '-'; + } + + low = (uint32_t) dec.low, midl = (uint32_t) (dec.low >> 32), + midh = (uint32_t) dec.high, high = (uint32_t) (dec.high >> 32); + + /* Decode combination field and exponent */ + combination = (high >> 26) & COMBINATION_MASK; + + if (JSONCONS_UNLIKELY ((combination >> 3) == 3)) { + /* Check for 'special' values */ + if (combination == COMBINATION_INFINITY) { /* Infinity */ + strcpy (str_out, BSON_DECIMAL128_INF); + return; + } else if (combination == COMBINATION_NAN) { /* NaN */ + /* first, not str_out, to erase the sign */ + strcpy (first, BSON_DECIMAL128_NAN); + /* we don't care about the NaN payload. */ + return; + } else { + biased_exponent = (high >> 15) & EXPONENT_MASK; + significand_msb = 0x8 + ((high >> 14) & 0x1); + } + } else { + significand_msb = (high >> 14) & 0x7; + biased_exponent = (high >> 17) & EXPONENT_MASK; + } + + exponent = biased_exponent - EXPONENT_BIAS; + /* Create string of significand digits */ + + /* Convert the 114-bit binary number represented by */ + /* (high, midh, midl, low) to at most 34 decimal */ + /* digits through modulo and division. */ + significand128.parts[0] = (high & 0x3fff) + ((significand_msb & 0xf) << 14); + significand128.parts[1] = midh; + significand128.parts[2] = midl; + significand128.parts[3] = low; + + if (significand128.parts[0] == 0 && significand128.parts[1] == 0 && + significand128.parts[2] == 0 && significand128.parts[3] == 0) { + is_zero = true; + } else if (significand128.parts[0] >= (1 << 17)) { + /* The significand is non-canonical or zero. + * In order to preserve compatibility with the densely packed decimal + * format, the maximum value for the significand of decimal128 is + * 1e34 - 1. If the value is greater than 1e34 - 1, the IEEE 754 + * standard dictates that the significand is interpreted as zero. + */ + is_zero = true; + } else { + for (k = 3; k >= 0; k--) { + uint32_t least_digits = 0; + detail::bson_uint128_divide1B ( + significand128, &significand128, &least_digits); + + /* We now have the 9 least significant digits (in base 2). */ + /* Convert and output to string. */ + if (!least_digits) { + continue; + } + + for (j = 8; j >= 0; j--) { + significand[k * 9 + j] = least_digits % 10; + least_digits /= 10; + } + } + } + + /* Output format options: */ + /* Scientific - [-]d.dddE(+/-)dd or [-]dE(+/-)dd */ + /* Regular - ddd.ddd */ + + if (is_zero) { + significand_digits = 1; + *significand_read = 0; + } else { + significand_digits = 36; + while (!(*significand_read)) { + significand_digits--; + significand_read++; + } + } + + scientific_exponent = significand_digits - 1 + exponent; + + /* The scientific exponent checks are dictated by the string conversion + * specification and are somewhat arbitrary cutoffs. + * + * We must check exponent > 0, because if this is the case, the number + * has trailing zeros. However, we *cannot* output these trailing zeros, + * because doing so would change the precision of the value, and would + * change stored data if the string converted number is round tripped. + */ + if (scientific_exponent < -6 || exponent > 0) { + /* Scientific format */ + *(str_out++) = *(significand_read++) + '0'; + significand_digits--; + + if (significand_digits) { + *(str_out++) = '.'; + } + + for (i = 0; i < significand_digits && (str_out - first) < 36; i++) { + *(str_out++) = *(significand_read++) + '0'; + } + /* Exponent */ + *(str_out++) = 'E'; + snprintf (str_out, 6, "%+d", scientific_exponent); + } else { + /* Regular format with no decimal place */ + if (exponent >= 0) { + for (i = 0; i < significand_digits && (str_out - first) < 36; i++) { + *(str_out++) = *(significand_read++) + '0'; + } + *str_out = '\0'; + } else { + int32_t radix_position = significand_digits + exponent; + + if (radix_position > 0) { /* non-zero digits before radix */ + for (i = 0; + i < radix_position && (str_out < last); + i++) { + *(str_out++) = *(significand_read++) + '0'; + } + } else { /* leading zero before radix point */ + *(str_out++) = '0'; + } + + *(str_out++) = '.'; + while (radix_position++ < 0) { /* add leading zeros after radix */ + *(str_out++) = '0'; + } + + for (i = 0; + (i < significand_digits - (std::max) (radix_position - 1, 0)) && + (str_out < last); + i++) { + *(str_out++) = *(significand_read++) + '0'; + } + *str_out = '\0'; + } + } + } + + + + /** + *------------------------------------------------------------------------------ + * + * bson_decimal128_from_string_w_len -- + * + * This function converts @string in the format [+-]ddd[.]ddd[E][+-]dddd to + * decimal128. Out of range values are converted to +/-Infinity. Invalid + * strings are converted to NaN. @len is the length of the string, or -1 + * meaning the string is null-terminated. + * + * If more digits are provided than the available precision allows, + * round to the nearest expressable decimal128 with ties going to even will + * occur. + * + * Note: @string must be ASCII only! + * + * Returns: + * true on success, or false on failure. @dec will be NaN if @str was invalid + * The &decimal128_t converted from @string at @dec. + * + * Side effects: + * None. + * + *------------------------------------------------------------------------------ + */ + + inline + bool decimal128_from_chars(const char* first, const char* last, decimal128_t& dec) + { + int len = last - first; + + bson_uint128_6464_t significand = {0}; + + const char* str_read = first; /* Read pointer for consuming str. */ + + /* Parsing state tracking */ + bool is_negative = false; + bool saw_radix = false; + bool includes_sign = false; /* True if the input first contains a sign. */ + bool found_nonzero = false; + + size_t significant_digits = 0; /* Total number of significant digits + * (no leading or trailing zero) */ + size_t ndigits_read = 0; /* Total number of significand digits read */ + size_t ndigits = 0; /* Total number of digits (no leading zeros) */ + size_t radix_position = 0; /* The number of the digits after radix */ + size_t first_nonzero = 0; /* The index of the first non-zero in *str* */ + + uint16_t digits[decimal128_limits::max_digits] = {0}; + uint16_t ndigits_stored = 0; /* The number of digits in digits */ + uint16_t *digits_insert = digits; /* Insertion pointer for digits */ + size_t first_digit = 0; /* The index of the first non-zero digit */ + size_t last_digit = 0; /* The index of the last digit */ + + int32_t exponent = 0; + uint64_t significand_high = 0; /* The high 17 digits of the significand */ + uint64_t significand_low = 0; /* The low 17 digits of the significand */ + uint16_t biased_exponent = 0; /* The biased exponent */ + + dec.high = 0; + dec.low = 0; + + if (*str_read == '+' || *str_read == '-') { + is_negative = *(str_read++) == '-'; + includes_sign = true; + } + + /* Check for Infinity or NaN */ + if (!isdigit (*str_read) && *str_read != '.') { + if (detail::dec128_istreq (str_read, "inf") || + detail::dec128_istreq (str_read, "infinity")) { + dec = is_negative ? decimal128_limits::neg_infinity : decimal128_limits::infinity; + return true; + } else if (detail::dec128_istreq (str_read, "nan")) { + dec = decimal128_limits::nan; + return true; + } + + dec = decimal128_limits::nan; + return false; + } + + /* Read digits */ + while (((isdigit (*str_read) || *str_read == '.')) && + (len == -1 || str_read < first + len)) { + if (*str_read == '.') { + if (saw_radix) { + dec = decimal128_limits::nan; + return false; + } + + saw_radix = true; + str_read++; + continue; + } + + if (ndigits_stored < 34) { + if (*str_read != '0' || found_nonzero) { + if (!found_nonzero) { + first_nonzero = ndigits_read; + } + + found_nonzero = true; + *(digits_insert++) = *(str_read) - '0'; /* Only store 34 digits */ + ndigits_stored++; + } + } + + if (found_nonzero) { + ndigits++; + } + + if (saw_radix) { + radix_position++; + } + + ndigits_read++; + str_read++; + } + + if (saw_radix && !ndigits_read) { + dec = decimal128_limits::nan; + return false; + } + + /* Read exponent if exists */ + if (*str_read == 'e' || *str_read == 'E') { + int nread = 0; + #ifdef _MSC_VER + #define SSCANF sscanf_s + #else + #define SSCANF sscanf + #endif + int read_exponent = SSCANF (++str_read, "%d%n", &exponent, &nread); + str_read += nread; + + if (!read_exponent || nread == 0) { + dec = decimal128_limits::nan; + return false; + } + + #undef SSCANF + } + + if ((len == -1 || str_read < first + len) && *str_read) { + dec = decimal128_limits::nan; + return false; + } + + /* Done reading input. */ + /* Find first non-zero digit in digits */ + first_digit = 0; + + if (!ndigits_stored) { /* value is zero */ + first_digit = 0; + last_digit = 0; + digits[0] = 0; + ndigits = 1; + ndigits_stored = 1; + significant_digits = 0; + } else { + last_digit = ndigits_stored - 1; + significant_digits = ndigits; + /* Mark trailing zeros as non-significant */ + while (first[first_nonzero + significant_digits - 1 + includes_sign + + saw_radix] == '0') { + significant_digits--; + } + } + + + /* Normalization of exponent */ + /* Correct exponent based on radix position, and shift significand as needed + */ + /* to represent user input */ + + /* Overflow prevention */ + if (exponent <= radix_position && radix_position - exponent > (1 << 14)) { + exponent = decimal128_limits::exponent_min; + } else { + exponent -= radix_position; + } + + /* Attempt to normalize the exponent */ + while (exponent > decimal128_limits::exponent_max) { + /* Shift exponent to significand and decrease */ + last_digit++; + + if (last_digit - first_digit > decimal128_limits::max_digits) { + /* The exponent is too great to shift into the significand. */ + if (significant_digits == 0) { + /* Value is zero, we are allowed to clamp the exponent. */ + exponent = decimal128_limits::exponent_max; + break; + } + + /* Overflow is not permitted, error. */ + dec = decimal128_limits::nan; + return false; + } + + exponent--; + } + + while (exponent < decimal128_limits::exponent_min || ndigits_stored < ndigits) { + /* Shift last digit */ + if (last_digit == 0) { + /* underflow is not allowed, but zero clamping is */ + if (significant_digits == 0) { + exponent = decimal128_limits::exponent_min; + break; + } + + dec = decimal128_limits::nan; + return false; + } + + if (ndigits_stored < ndigits) { + if (first[ndigits - 1 + includes_sign + saw_radix] - '0' != 0 && + significant_digits != 0) { + dec = decimal128_limits::nan; + return false; + } + + ndigits--; /* adjust to match digits not stored */ + } else { + if (digits[last_digit] != 0) { + /* Inexact rounding is not allowed. */ + dec = decimal128_limits::nan; + return false; + } + + + last_digit--; /* adjust to round */ + } + + if (exponent < decimal128_limits::exponent_max) { + exponent++; + } else { + dec = decimal128_limits::nan; + return false; + } + } + + /* Round */ + /* We've normalized the exponent, but might still need to round. */ + if (last_digit - first_digit + 1 < significant_digits) { + uint8_t round_digit; + + /* There are non-zero digits after last_digit that need rounding. */ + /* We round to nearest, ties to even */ + round_digit = + first[first_nonzero + last_digit + includes_sign + saw_radix + 1] - + '0'; + + if (round_digit != 0) { + /* Inexact (non-zero) rounding is not allowed */ + dec = decimal128_limits::nan; + return false; + } + } + + /* Encode significand */ + significand_high = 0, /* The high 17 digits of the significand */ + significand_low = 0; /* The low 17 digits of the significand */ + + if (significant_digits == 0) { /* read a zero */ + significand_high = 0; + significand_low = 0; + } else if (last_digit - first_digit < 17) { + size_t d_idx = first_digit; + significand_low = digits[d_idx++]; + + for (; d_idx <= last_digit; d_idx++) { + significand_low *= 10; + significand_low += digits[d_idx]; + significand_high = 0; + } + } else { + size_t d_idx = first_digit; + significand_high = digits[d_idx++]; + + for (; d_idx <= last_digit - 17; d_idx++) { + significand_high *= 10; + significand_high += digits[d_idx]; + } + + significand_low = digits[d_idx++]; + + for (; d_idx <= last_digit; d_idx++) { + significand_low *= 10; + significand_low += digits[d_idx]; + } + } + + detail::mul_64x64 (significand_high, 100000000000000000ull, &significand); + significand.low += significand_low; + + if (significand.low < significand_low) { + significand.high += 1; + } + + + biased_exponent = (exponent + (int16_t) decimal128_limits::exponent_bias); + + /* Encode combination, exponent, and significand. */ + if ((significand.high >> 49) & 1) { + /* Encode '11' into bits 1 to 3 */ + dec.high |= (0x3ull << 61); + dec.high |= (biased_exponent & 0x3fffull) << 47; + dec.high |= significand.high & 0x7fffffffffffull; + } else { + dec.high |= (biased_exponent & 0x3fffull) << 49; + dec.high |= significand.high & 0x1ffffffffffffull; + } + + dec.low = significand.low; + + /* Encode sign */ + if (is_negative) { + dec.high |= 0x8000000000000000ull; + } + + return true; + } + +} // namespace bson +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/bson/bson_encoder.hpp b/third_party/jsoncons_ext/bson/bson_encoder.hpp new file mode 100644 index 0000000000..52d0a804eb --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_encoder.hpp @@ -0,0 +1,575 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_ENCODER_HPP +#define JSONCONS_BSON_BSON_ENCODER_HPP + +#include +#include +#include // std::numeric_limits +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace bson { + +template > +class basic_bson_encoder final : public basic_json_visitor +{ + enum class decimal_parse_state { start, integer, exp1, exp2, fraction1 }; + static constexpr int64_t nanos_in_milli = 1000000; + static constexpr int64_t nanos_in_second = 1000000000; + static constexpr int64_t millis_in_second = 1000; +public: + using allocator_type = Allocator; + using char_type = char; + using typename basic_json_visitor::string_view_type; + using sink_type = Sink; + +private: + struct stack_item + { + jsoncons::bson::bson_container_type type_; + std::size_t offset_; + std::size_t name_offset_; + std::size_t index_; + + stack_item(jsoncons::bson::bson_container_type type, std::size_t offset) noexcept + : type_(type), offset_(offset), name_offset_(0), index_(0) + { + } + + std::size_t offset() const + { + return offset_; + } + + std::size_t member_offset() const + { + return name_offset_; + } + + void member_offset(std::size_t offset) + { + name_offset_ = offset; + } + + std::size_t next_index() + { + return index_++; + } + + bool is_object() const + { + return type_ == jsoncons::bson::bson_container_type::document; + } + + + }; + + sink_type sink_; + const bson_encode_options options_; + allocator_type alloc_; + + std::vector stack_; + std::vector buffer_; + int nesting_depth_; + + // Noncopyable and nonmoveable + basic_bson_encoder(const basic_bson_encoder&) = delete; + basic_bson_encoder& operator=(const basic_bson_encoder&) = delete; +public: + explicit basic_bson_encoder(Sink&& sink, + const Allocator& alloc = Allocator()) + : basic_bson_encoder(std::forward(sink), + bson_encode_options(), + alloc) + { + } + + explicit basic_bson_encoder(Sink&& sink, + const bson_encode_options& options, + const Allocator& alloc = Allocator()) + : sink_(std::forward(sink)), + options_(options), + alloc_(alloc), + nesting_depth_(0) + { + } + + ~basic_bson_encoder() noexcept + { + sink_.flush(); + } + + void reset() + { + stack_.clear(); + buffer_.clear(); + nesting_depth_ = 0; + } + + void reset(Sink&& sink) + { + sink_ = std::move(sink); + reset(); + } + +private: + // Implementing methods + + void visit_flush() override + { + sink_.flush(); + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = bson_errc::max_nesting_depth_exceeded; + return false; + } + if (buffer_.size() > 0) + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + before_value(jsoncons::bson::bson_type::document_type); + } + + stack_.emplace_back(jsoncons::bson::bson_container_type::document, buffer_.size()); + buffer_.insert(buffer_.end(), sizeof(int32_t), 0); + + return true; + } + + bool visit_end_object(const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + buffer_.push_back(0x00); + + std::size_t length = buffer_.size() - stack_.back().offset(); + binary::native_to_little(static_cast(length), buffer_.begin()+stack_.back().offset()); + + stack_.pop_back(); + if (stack_.empty()) + { + for (auto c : buffer_) + { + sink_.push_back(c); + } + } + return true; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = bson_errc::max_nesting_depth_exceeded; + return false; + } + if (buffer_.size() > 0) + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + before_value(jsoncons::bson::bson_type::array_type); + } + stack_.emplace_back(jsoncons::bson::bson_container_type::array, buffer_.size()); + buffer_.insert(buffer_.end(), sizeof(int32_t), 0); + return true; + } + + bool visit_end_array(const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + buffer_.push_back(0x00); + + std::size_t length = buffer_.size() - stack_.back().offset(); + binary::native_to_little(static_cast(length), buffer_.begin()+stack_.back().offset()); + + stack_.pop_back(); + if (stack_.empty()) + { + for (auto c : buffer_) + { + sink_.push_back(c); + } + } + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) override + { + stack_.back().member_offset(buffer_.size()); + buffer_.push_back(0x00); // reserve space for code + for (auto c : name) + { + buffer_.push_back(c); + } + buffer_.push_back(0x00); + return true; + } + + bool visit_null(semantic_tag tag, const ser_context&, std::error_code& ec) override + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + switch (tag) + { + case semantic_tag::undefined: + before_value(jsoncons::bson::bson_type::undefined_type); + break; + default: + before_value(jsoncons::bson::bson_type::null_type); + break; + } + return true; + } + + bool visit_bool(bool val, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + before_value(jsoncons::bson::bson_type::bool_type); + if (val) + { + buffer_.push_back(0x01); + } + else + { + buffer_.push_back(0x00); + } + + return true; + } + + bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code& ec) override + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + + switch (tag) + { + case semantic_tag::float128: + { + before_value(jsoncons::bson::bson_type::decimal128_type); + decimal128_t dec; + auto rc = decimal128_from_chars(sv.data(), sv.data()+sv.size(), dec); + if (rc.ec != std::errc()) + { + ec = bson_errc::invalid_decimal128_string; + return false; + } + binary::native_to_little(dec.low,std::back_inserter(buffer_)); + binary::native_to_little(dec.high,std::back_inserter(buffer_)); + break; + } + case semantic_tag::id: + { + before_value(jsoncons::bson::bson_type::object_id_type); + oid_t oid(sv); + for (auto b : oid) + { + buffer_.push_back(b); + } + break; + } + case semantic_tag::regex: + { + before_value(jsoncons::bson::bson_type::regex_type); + std::size_t first = sv.find_first_of('/'); + std::size_t last = sv.find_last_of('/'); + if (first == string_view::npos || last == string_view::npos || first == last) + { + ec = bson_errc::invalid_regex_string; + return false; + } + string_view regex = sv.substr(first+1,last-1); + for (auto c : regex) + { + buffer_.push_back(c); + } + buffer_.push_back(0x00); + string_view options = sv.substr(last+1); + for (auto c : options) + { + buffer_.push_back(c); + } + buffer_.push_back(0x00); + break; + } + default: + switch (tag) + { + case semantic_tag::code: + before_value(jsoncons::bson::bson_type::javascript_type); + break; + default: + before_value(jsoncons::bson::bson_type::string_type); + break; + } + std::size_t offset = buffer_.size(); + buffer_.insert(buffer_.end(), sizeof(int32_t), 0); + std::size_t string_offset = buffer_.size(); + auto sink = unicode_traits::validate(sv.data(), sv.size()); + if (sink.ec != unicode_traits::conv_errc()) + { + ec = bson_errc::invalid_utf8_text_string; + return false; + } + for (auto c : sv) + { + buffer_.push_back(c); + } + buffer_.push_back(0x00); + std::size_t length = buffer_.size() - string_offset; + binary::native_to_little(static_cast(length), buffer_.begin()+offset); + break; + } + + return true; + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag, + const ser_context&, + std::error_code& ec) override + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + before_value(jsoncons::bson::bson_type::binary_type); + + std::size_t offset = buffer_.size(); + buffer_.insert(buffer_.end(), sizeof(int32_t), 0); + std::size_t string_offset = buffer_.size(); + + buffer_.push_back(0x80); // default subtype + + for (auto c : b) + { + buffer_.push_back(c); + } + std::size_t length = buffer_.size() - string_offset - 1; + binary::native_to_little(static_cast(length), buffer_.begin()+offset); + + return true; + } + + bool visit_byte_string(const byte_string_view& b, + uint64_t ext_tag, + const ser_context&, + std::error_code& ec) override + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + before_value(jsoncons::bson::bson_type::binary_type); + + std::size_t offset = buffer_.size(); + buffer_.insert(buffer_.end(), sizeof(int32_t), 0); + std::size_t string_offset = buffer_.size(); + + buffer_.push_back(static_cast(ext_tag)); // default subtype + + for (auto c : b) + { + buffer_.push_back(c); + } + std::size_t length = buffer_.size() - string_offset - 1; + binary::native_to_little(static_cast(length), buffer_.begin()+offset); + + return true; + } + + bool visit_int64(int64_t val, + semantic_tag tag, + const ser_context&, + std::error_code& ec) override + { + static constexpr int64_t min_value_div_1000 = (std::numeric_limits::min)() / 1000; + static constexpr int64_t max_value_div_1000 = (std::numeric_limits::max)() / 1000; + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + + switch (tag) + { + case semantic_tag::epoch_second: + if (val < min_value_div_1000) + { + ec = bson_errc::datetime_too_small; + return false; + } + if (val > max_value_div_1000) + { + ec = bson_errc::datetime_too_large; + return false; + } + before_value(jsoncons::bson::bson_type::datetime_type); + binary::native_to_little(val*millis_in_second,std::back_inserter(buffer_)); + return true; + case semantic_tag::epoch_milli: + before_value(jsoncons::bson::bson_type::datetime_type); + binary::native_to_little(val,std::back_inserter(buffer_)); + return true; + case semantic_tag::epoch_nano: + before_value(jsoncons::bson::bson_type::datetime_type); + if (val != 0) + { + val /= nanos_in_milli; + } + binary::native_to_little(static_cast(val),std::back_inserter(buffer_)); + return true; + default: + { + if (val >= (std::numeric_limits::lowest)() && val <= (std::numeric_limits::max)()) + { + before_value(jsoncons::bson::bson_type::int32_type); + binary::native_to_little(static_cast(val),std::back_inserter(buffer_)); + } + else + { + before_value(jsoncons::bson::bson_type::int64_type); + binary::native_to_little(static_cast(val),std::back_inserter(buffer_)); + } + return true; + } + } + } + + bool visit_uint64(uint64_t val, + semantic_tag tag, + const ser_context&, + std::error_code& ec) override + { + static constexpr uint64_t max_value_div_1000 = (std::numeric_limits::max)() / 1000; + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + + switch (tag) + { + case semantic_tag::epoch_second: + if (val > max_value_div_1000) + { + ec = bson_errc::datetime_too_large; + return false; + } + before_value(jsoncons::bson::bson_type::datetime_type); + binary::native_to_little(static_cast(val*millis_in_second),std::back_inserter(buffer_)); + return true; + case semantic_tag::epoch_milli: + before_value(jsoncons::bson::bson_type::datetime_type); + binary::native_to_little(static_cast(val),std::back_inserter(buffer_)); + return true; + case semantic_tag::epoch_nano: + before_value(jsoncons::bson::bson_type::datetime_type); + if (val != 0) + { + val /= nanos_in_second; + } + binary::native_to_little(static_cast(val),std::back_inserter(buffer_)); + return true; + default: + { + bool more; + if (val <= static_cast((std::numeric_limits::max)())) + { + before_value(jsoncons::bson::bson_type::int32_type); + binary::native_to_little(static_cast(val),std::back_inserter(buffer_)); + more = true; + } + else if (val <= static_cast((std::numeric_limits::max)())) + { + before_value(jsoncons::bson::bson_type::int64_type); + binary::native_to_little(static_cast(val),std::back_inserter(buffer_)); + more = true; + } + else + { + ec = bson_errc::number_too_large; + more = false; + } + return more; + } + } + } + + bool visit_double(double val, + semantic_tag, + const ser_context&, + std::error_code& ec) override + { + if (stack_.empty()) + { + ec = bson_errc::expected_bson_document; + return false; + } + before_value(jsoncons::bson::bson_type::double_type); + binary::native_to_little(val,std::back_inserter(buffer_)); + return true; + } + + void before_value(uint8_t code) + { + JSONCONS_ASSERT(!stack_.empty()); + if (stack_.back().is_object()) + { + buffer_[stack_.back().member_offset()] = code; + } + else + { + buffer_.push_back(code); + std::string name = std::to_string(stack_.back().next_index()); + buffer_.insert(buffer_.end(), name.begin(), name.end()); + buffer_.push_back(0x00); + } + } +}; + +using bson_stream_encoder = basic_bson_encoder; +using bson_bytes_encoder = basic_bson_encoder>>; + +}} +#endif diff --git a/third_party/jsoncons_ext/bson/bson_error.hpp b/third_party/jsoncons_ext/bson/bson_error.hpp new file mode 100644 index 0000000000..bb86e36611 --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_error.hpp @@ -0,0 +1,103 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_ERROR_HPP +#define JSONCONS_BSON_BSON_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace bson { + +enum class bson_errc +{ + success = 0, + unexpected_eof = 1, + source_error, + invalid_utf8_text_string, + max_nesting_depth_exceeded, + string_length_is_non_positive, + length_is_negative, + number_too_large, + invalid_decimal128_string, + datetime_too_small, + datetime_too_large, + expected_bson_document, + invalid_regex_string, + size_mismatch, + unknown_type +}; + +class bson_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/bson"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case bson_errc::unexpected_eof: + return "Unexpected end of file"; + case bson_errc::source_error: + return "Source error"; + case bson_errc::invalid_utf8_text_string: + return "Illegal UTF-8 encoding in text string"; + case bson_errc::max_nesting_depth_exceeded: + return "Data item nesting exceeds limit in options"; + case bson_errc::string_length_is_non_positive: + return "Request for the length of a string returned a non-positive result"; + case bson_errc::length_is_negative: + return "Request for the length of a binary returned a negative result"; + case bson_errc::unknown_type: + return "An unknown type was found in the stream"; + case bson_errc::number_too_large: + return "Number too large"; + case bson_errc::invalid_decimal128_string: + return "Invalid decimal128 string"; + case bson_errc::datetime_too_large: + return "datetime too large"; + case bson_errc::datetime_too_small: + return "datetime too small"; + case bson_errc::expected_bson_document: + return "Expected BSON document"; + case bson_errc::invalid_regex_string: + return "Invalid regex string"; + case bson_errc::size_mismatch: + return "Document or array size doesn't match bytes read"; + default: + return "Unknown BSON parser error"; + } + } +}; + +inline +const std::error_category& bson_error_category() +{ + static bson_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(bson_errc result) +{ + return std::error_code(static_cast(result),bson_error_category()); +} + + +}} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons_ext/bson/bson_oid.hpp b/third_party/jsoncons_ext/bson/bson_oid.hpp new file mode 100644 index 0000000000..065d8da128 --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_oid.hpp @@ -0,0 +1,245 @@ +#ifndef JSONCONS_BSON_BSON_OID_HPP +#define JSONCONS_BSON_BSON_OID_HPP + +/* + * Implements class oid_t and non member function bson_oid_to_string + * + * Based on the libjson functions bson_oid_to_string + * and bson_oid_init_from_string_unsafe , available at + * https://github.com/mongodb/mongo-c-driver/blob/master/src/libbson/src/bson/bson-oid.h + * and https://github.com/mongodb/mongo-c-driver/blob/master/src/libbson/src/bson/bson-oid.c + * +*/ + +/* + * Copyright 2015 MongoDB, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace bson { + + class oid_t + { + std::array bytes_; + public: + using iterator = std::array::iterator; + using const_iterator = std::array::const_iterator; + + oid_t(const std::array& bytes) + : bytes_(bytes) + { + } + oid_t(uint8_t data[12]) + { + std::memcpy(bytes_.data(),data,12); + } + + oid_t(const string_view& str) + { + for (std::size_t i = 0; i < bytes_.size(); i++) + { + bytes_[i] = ((parse_hex_char (str[2 * i]) << 4) | + (parse_hex_char (str[2 * i + 1]))); + } + } + + const uint8_t* data() const + { + return bytes_.data(); + } + + std::size_t size() const + { + return bytes_.size(); + } + + iterator begin() + { + return bytes_.begin(); + } + + iterator end() + { + return bytes_.end(); + } + + const_iterator begin() const + { + return bytes_.begin(); + } + + const_iterator end() const + { + return bytes_.end(); + } + + private: + + static uint8_t parse_hex_char (char hex) + { + switch (hex) { + case '0': + return 0; + case '1': + return 1; + case '2': + return 2; + case '3': + return 3; + case '4': + return 4; + case '5': + return 5; + case '6': + return 6; + case '7': + return 7; + case '8': + return 8; + case '9': + return 9; + case 'a': + case 'A': + return 0xa; + case 'b': + case 'B': + return 0xb; + case 'c': + case 'C': + return 0xc; + case 'd': + case 'D': + return 0xd; + case 'e': + case 'E': + return 0xe; + case 'f': + case 'F': + return 0xf; + default: + return 0; + } + } + }; + + namespace detail { + + inline + const uint16_t* get_hex_char_pairs(std::true_type) // big endian + { + static const uint16_t hex_char_pairs[] = { + 12336, 12337, 12338, 12339, 12340, 12341, 12342, 12343, 12344, 12345, 12385, + 12386, 12387, 12388, 12389, 12390, 12592, 12593, 12594, 12595, 12596, 12597, + 12598, 12599, 12600, 12601, 12641, 12642, 12643, 12644, 12645, 12646, 12848, + 12849, 12850, 12851, 12852, 12853, 12854, 12855, 12856, 12857, 12897, 12898, + 12899, 12900, 12901, 12902, 13104, 13105, 13106, 13107, 13108, 13109, 13110, + 13111, 13112, 13113, 13153, 13154, 13155, 13156, 13157, 13158, 13360, 13361, + 13362, 13363, 13364, 13365, 13366, 13367, 13368, 13369, 13409, 13410, 13411, + 13412, 13413, 13414, 13616, 13617, 13618, 13619, 13620, 13621, 13622, 13623, + 13624, 13625, 13665, 13666, 13667, 13668, 13669, 13670, 13872, 13873, 13874, + 13875, 13876, 13877, 13878, 13879, 13880, 13881, 13921, 13922, 13923, 13924, + 13925, 13926, 14128, 14129, 14130, 14131, 14132, 14133, 14134, 14135, 14136, + 14137, 14177, 14178, 14179, 14180, 14181, 14182, 14384, 14385, 14386, 14387, + 14388, 14389, 14390, 14391, 14392, 14393, 14433, 14434, 14435, 14436, 14437, + 14438, 14640, 14641, 14642, 14643, 14644, 14645, 14646, 14647, 14648, 14649, + 14689, 14690, 14691, 14692, 14693, 14694, 24880, 24881, 24882, 24883, 24884, + 24885, 24886, 24887, 24888, 24889, 24929, 24930, 24931, 24932, 24933, 24934, + 25136, 25137, 25138, 25139, 25140, 25141, 25142, 25143, 25144, 25145, 25185, + 25186, 25187, 25188, 25189, 25190, 25392, 25393, 25394, 25395, 25396, 25397, + 25398, 25399, 25400, 25401, 25441, 25442, 25443, 25444, 25445, 25446, 25648, + 25649, 25650, 25651, 25652, 25653, 25654, 25655, 25656, 25657, 25697, 25698, + 25699, 25700, 25701, 25702, 25904, 25905, 25906, 25907, 25908, 25909, 25910, + 25911, 25912, 25913, 25953, 25954, 25955, 25956, 25957, 25958, 26160, 26161, + 26162, 26163, 26164, 26165, 26166, 26167, 26168, 26169, 26209, 26210, 26211, + 26212, 26213, 26214}; + + return hex_char_pairs; + } + + inline + const uint16_t* get_hex_char_pairs(std::false_type) // little endian + { + static const uint16_t hex_char_pairs[] = { + 12336, 12592, 12848, 13104, 13360, 13616, 13872, 14128, 14384, 14640, 24880, + 25136, 25392, 25648, 25904, 26160, 12337, 12593, 12849, 13105, 13361, 13617, + 13873, 14129, 14385, 14641, 24881, 25137, 25393, 25649, 25905, 26161, 12338, + 12594, 12850, 13106, 13362, 13618, 13874, 14130, 14386, 14642, 24882, 25138, + 25394, 25650, 25906, 26162, 12339, 12595, 12851, 13107, 13363, 13619, 13875, + 14131, 14387, 14643, 24883, 25139, 25395, 25651, 25907, 26163, 12340, 12596, + 12852, 13108, 13364, 13620, 13876, 14132, 14388, 14644, 24884, 25140, 25396, + 25652, 25908, 26164, 12341, 12597, 12853, 13109, 13365, 13621, 13877, 14133, + 14389, 14645, 24885, 25141, 25397, 25653, 25909, 26165, 12342, 12598, 12854, + 13110, 13366, 13622, 13878, 14134, 14390, 14646, 24886, 25142, 25398, 25654, + 25910, 26166, 12343, 12599, 12855, 13111, 13367, 13623, 13879, 14135, 14391, + 14647, 24887, 25143, 25399, 25655, 25911, 26167, 12344, 12600, 12856, 13112, + 13368, 13624, 13880, 14136, 14392, 14648, 24888, 25144, 25400, 25656, 25912, + 26168, 12345, 12601, 12857, 13113, 13369, 13625, 13881, 14137, 14393, 14649, + 24889, 25145, 25401, 25657, 25913, 26169, 12385, 12641, 12897, 13153, 13409, + 13665, 13921, 14177, 14433, 14689, 24929, 25185, 25441, 25697, 25953, 26209, + 12386, 12642, 12898, 13154, 13410, 13666, 13922, 14178, 14434, 14690, 24930, + 25186, 25442, 25698, 25954, 26210, 12387, 12643, 12899, 13155, 13411, 13667, + 13923, 14179, 14435, 14691, 24931, 25187, 25443, 25699, 25955, 26211, 12388, + 12644, 12900, 13156, 13412, 13668, 13924, 14180, 14436, 14692, 24932, 25188, + 25444, 25700, 25956, 26212, 12389, 12645, 12901, 13157, 13413, 13669, 13925, + 14181, 14437, 14693, 24933, 25189, 25445, 25701, 25957, 26213, 12390, 12646, + 12902, 13158, 13414, 13670, 13926, 14182, 14438, 14694, 24934, 25190, 25446, + 25702, 25958, 26214}; + + return hex_char_pairs; + } + + inline + void init_hex_char_pairs(const oid_t& oid, uint16_t* data) + { + const uint8_t* bytes = oid.data(); + const uint16_t* gHexCharPairs = get_hex_char_pairs(std::integral_constant()); + + data[0] = gHexCharPairs[bytes[0]]; + data[1] = gHexCharPairs[bytes[1]]; + data[2] = gHexCharPairs[bytes[2]]; + data[3] = gHexCharPairs[bytes[3]]; + data[4] = gHexCharPairs[bytes[4]]; + data[5] = gHexCharPairs[bytes[5]]; + data[6] = gHexCharPairs[bytes[6]]; + data[7] = gHexCharPairs[bytes[7]]; + data[8] = gHexCharPairs[bytes[8]]; + data[9] = gHexCharPairs[bytes[9]]; + data[10] = gHexCharPairs[bytes[10]]; + data[11] = gHexCharPairs[bytes[11]]; + } + + } // namsepace detail + + template + inline + void to_string(const oid_t& oid, StringT& s) + { + s.resize(24); + detail::init_hex_char_pairs(oid, reinterpret_cast(&s[0])); + } + +} // namespace bson +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/bson/bson_options.hpp b/third_party/jsoncons_ext/bson/bson_options.hpp new file mode 100644 index 0000000000..b0180c46ad --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_options.hpp @@ -0,0 +1,75 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_OPTIONS_HPP +#define JSONCONS_BSON_BSON_OPTIONS_HPP + +#include +#include // std::numeric_limits +#include +#include +#include + +namespace jsoncons { namespace bson { + +class bson_options; + +class bson_options_common +{ + friend class bson_options; + + int max_nesting_depth_; +protected: + virtual ~bson_options_common() = default; + + bson_options_common() + : max_nesting_depth_(1024) + { + } + + bson_options_common(const bson_options_common&) = default; + bson_options_common& operator=(const bson_options_common&) = default; + bson_options_common(bson_options_common&&) = default; + bson_options_common& operator=(bson_options_common&&) = default; +public: + int max_nesting_depth() const + { + return max_nesting_depth_; + } +}; + +class bson_decode_options : public virtual bson_options_common +{ + friend class bson_options; +public: + bson_decode_options() + { + } +}; + +class bson_encode_options : public virtual bson_options_common +{ + friend class bson_options; +public: + bson_encode_options() + { + } +}; + +class bson_options final : public bson_decode_options, public bson_encode_options +{ +public: + using bson_options_common::max_nesting_depth; + + bson_options& max_nesting_depth(int value) + { + this->max_nesting_depth_ = value; + return *this; + } +}; + +}} +#endif diff --git a/third_party/jsoncons_ext/bson/bson_parser.hpp b/third_party/jsoncons_ext/bson/bson_parser.hpp new file mode 100644 index 0000000000..164858f232 --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_parser.hpp @@ -0,0 +1,650 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_PARSER_HPP +#define JSONCONS_BSON_BSON_PARSER_HPP + +#include +#include +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace bson { + +enum class parse_mode {root,accept,document,array,value}; + +struct parse_state +{ + parse_mode mode; + std::size_t length; + std::size_t pos; + uint8_t type; + std::size_t index; + + parse_state(parse_mode mode_, std::size_t length_, std::size_t pos_, uint8_t type_ = 0) noexcept + : mode(mode_), length(length_), pos(pos_), type(type_), index(0) + { + } + + parse_state(const parse_state&) = default; + parse_state(parse_state&&) = default; + parse_state& operator=(const parse_state&) = default; + parse_state& operator=(parse_state&&) = default; +}; + +template > +class basic_bson_parser : public ser_context +{ + using char_type = char; + using char_traits_type = std::char_traits; + using temp_allocator_type = TempAllocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using parse_state_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using string_type = std::basic_string,char_allocator_type>; + + Source source_; + bson_decode_options options_; + bool more_; + bool done_; + std::vector bytes_buffer_; + string_type name_buffer_; + string_type text_buffer_; + std::vector state_stack_; +public: + template + basic_bson_parser(Sourceable&& source, + const bson_decode_options& options = bson_decode_options(), + const TempAllocator& temp_alloc = TempAllocator()) + : source_(std::forward(source)), + options_(options), + more_(true), + done_(false), + bytes_buffer_(temp_alloc), + name_buffer_(temp_alloc), + text_buffer_(temp_alloc), + state_stack_(temp_alloc) + { + state_stack_.emplace_back(parse_mode::root,0,0); + } + + void restart() + { + more_ = true; + } + + void reset() + { + more_ = true; + done_ = false; + bytes_buffer_.clear(); + name_buffer_.clear(); + text_buffer_.clear(); + state_stack_.clear(); + state_stack_.emplace_back(parse_mode::root,0,0); + } + + template + void reset(Sourceable&& source) + { + source_ = std::forward(source); + reset(); + } + + bool done() const + { + return done_; + } + + bool stopped() const + { + return !more_; + } + + std::size_t line() const override + { + return 0; + } + + std::size_t column() const override + { + return source_.position(); + } + + void array_expected(json_visitor& visitor, std::error_code& ec) + { + if (state_stack_.size() == 2 && state_stack_.back().mode == parse_mode::document) + { + state_stack_.back().mode = parse_mode::array; + more_ = visitor.begin_array(semantic_tag::none, *this, ec); + } + } + + void parse(json_visitor& visitor, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(source_.is_error())) + { + ec = bson_errc::source_error; + more_ = false; + return; + } + + while (!done_ && more_) + { + switch (state_stack_.back().mode) + { + case parse_mode::root: + state_stack_.back().mode = parse_mode::accept; + begin_document(visitor, ec); + break; + case parse_mode::document: + { + uint8_t type; + std::size_t n = source_.read(&type, 1); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != 1)) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + if (type != 0x00) + { + read_e_name(visitor,jsoncons::bson::bson_container_type::document,ec); + state_stack_.back().mode = parse_mode::value; + state_stack_.back().type = type; + } + else + { + end_document(visitor,ec); + } + break; + } + case parse_mode::array: + { + uint8_t type; + std::size_t n = source_.read(&type, 1); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != 1)) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + if (type != 0x00) + { + read_e_name(visitor,jsoncons::bson::bson_container_type::array,ec); + read_value(visitor, type, ec); + } + else + { + end_array(visitor,ec); + } + break; + } + case parse_mode::value: + state_stack_.back().mode = parse_mode::document; + read_value(visitor,state_stack_.back().type,ec); + break; + case parse_mode::accept: + { + JSONCONS_ASSERT(state_stack_.size() == 1); + state_stack_.clear(); + more_ = false; + done_ = true; + visitor.flush(); + break; + } + } + } + } + +private: + + void begin_document(json_visitor& visitor, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(static_cast(state_stack_.size()) > options_.max_nesting_depth())) + { + ec = bson_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + + uint8_t buf[sizeof(int32_t)]; + size_t n = source_.read(buf, sizeof(int32_t)); + if (JSONCONS_UNLIKELY(n != sizeof(int32_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + + auto length = binary::little_to_native(buf, sizeof(buf)); + + more_ = visitor.begin_object(semantic_tag::none, *this, ec); + state_stack_.emplace_back(parse_mode::document,length,n); + } + + void end_document(json_visitor& visitor, std::error_code& ec) + { + JSONCONS_ASSERT(state_stack_.size() >= 2); + + more_ = visitor.end_object(*this,ec); + if (JSONCONS_UNLIKELY(state_stack_.back().pos != state_stack_.back().length)) + { + ec = bson_errc::size_mismatch; + more_ = false; + return; + } + std::size_t pos = state_stack_.back().pos; + state_stack_.pop_back(); + state_stack_.back().pos += pos; + } + + void begin_array(json_visitor& visitor, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(static_cast(state_stack_.size()) > options_.max_nesting_depth())) + { + ec = bson_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + uint8_t buf[sizeof(int32_t)]; + std::size_t n = source_.read(buf, sizeof(int32_t)); + if (JSONCONS_UNLIKELY(n != sizeof(int32_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + auto length = binary::little_to_native(buf, sizeof(buf)); + + more_ = visitor.begin_array(semantic_tag::none, *this, ec); + if (ec) + { + return; + } + state_stack_.emplace_back(parse_mode::array, length, n); + } + + void end_array(json_visitor& visitor, std::error_code& ec) + { + JSONCONS_ASSERT(state_stack_.size() >= 2); + + more_ = visitor.end_array(*this, ec); + if (JSONCONS_UNLIKELY(state_stack_.back().pos != state_stack_.back().length)) + { + ec = bson_errc::size_mismatch; + more_ = false; + return; + } + std::size_t pos = state_stack_.back().pos; + state_stack_.pop_back(); + state_stack_.back().pos += pos; + } + + void read_e_name(json_visitor& visitor, jsoncons::bson::bson_container_type type, std::error_code& ec) + { + name_buffer_.clear(); + read_cstring(name_buffer_, ec); + if (ec) + { + return; + } + if (type == jsoncons::bson::bson_container_type::document) + { + auto result = unicode_traits::validate(name_buffer_.data(),name_buffer_.size()); + if (JSONCONS_UNLIKELY(result.ec != unicode_traits::conv_errc())) + { + ec = bson_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.key(jsoncons::basic_string_view(name_buffer_.data(),name_buffer_.length()), *this, ec); + } + } + + void read_value(json_visitor& visitor, uint8_t type, std::error_code& ec) + { + switch (type) + { + case jsoncons::bson::bson_type::double_type: + { + uint8_t buf[sizeof(double)]; + std::size_t n = source_.read(buf, sizeof(double)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(double))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + double res = binary::little_to_native(buf, sizeof(buf)); + more_ = visitor.double_value(res, semantic_tag::none, *this, ec); + break; + } + case jsoncons::bson::bson_type::symbol_type: + case jsoncons::bson::bson_type::min_key_type: + case jsoncons::bson::bson_type::max_key_type: + case jsoncons::bson::bson_type::string_type: + { + text_buffer_.clear(); + read_string(text_buffer_, ec); + if (ec) + { + return; + } + auto result = unicode_traits::validate(text_buffer_.data(), text_buffer_.size()); + if (JSONCONS_UNLIKELY(result.ec != unicode_traits::conv_errc())) + { + ec = bson_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.string_value(text_buffer_, semantic_tag::none, *this, ec); + break; + } + case jsoncons::bson::bson_type::javascript_type: + { + text_buffer_.clear(); + read_string(text_buffer_, ec); + if (ec) + { + return; + } + auto result = unicode_traits::validate(text_buffer_.data(), text_buffer_.size()); + if (JSONCONS_UNLIKELY(result.ec != unicode_traits::conv_errc())) + { + ec = bson_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.string_value(text_buffer_, semantic_tag::code, *this, ec); + break; + } + case jsoncons::bson::bson_type::regex_type: + { + text_buffer_.clear(); + text_buffer_.push_back('/'); + read_cstring(text_buffer_, ec); + if (ec) + { + return; + } + text_buffer_.push_back('/'); + read_cstring(text_buffer_, ec); + if (ec) + { + return; + } + more_ = visitor.string_value(text_buffer_, semantic_tag::regex, *this, ec); + break; + } + case jsoncons::bson::bson_type::document_type: + { + begin_document(visitor,ec); + break; + } + + case jsoncons::bson::bson_type::array_type: + { + begin_array(visitor,ec); + break; + } + case jsoncons::bson::bson_type::undefined_type: + { + more_ = visitor.null_value(semantic_tag::undefined, *this, ec); + break; + } + case jsoncons::bson::bson_type::null_type: + { + more_ = visitor.null_value(semantic_tag::none, *this, ec); + break; + } + case jsoncons::bson::bson_type::bool_type: + { + uint8_t c; + std::size_t n = source_.read(&c, 1); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != 1)) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + more_ = visitor.bool_value(c != 0, semantic_tag::none, *this, ec); + break; + } + case jsoncons::bson::bson_type::int32_type: + { + uint8_t buf[sizeof(int32_t)]; + std::size_t n = source_.read(buf, sizeof(int32_t)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(int32_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + auto val = binary::little_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::bson::bson_type::timestamp_type: + { + uint8_t buf[sizeof(uint64_t)]; + std::size_t n = source_.read(buf, sizeof(uint64_t)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(uint64_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + auto val = binary::little_to_native(buf, sizeof(buf)); + more_ = visitor.uint64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::bson::bson_type::int64_type: + { + uint8_t buf[sizeof(int64_t)]; + std::size_t n = source_.read(buf, sizeof(int64_t)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(int64_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + auto val = binary::little_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::bson::bson_type::datetime_type: + { + uint8_t buf[sizeof(int64_t)]; + std::size_t n = source_.read(buf, sizeof(int64_t)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(int64_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + auto val = binary::little_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::epoch_milli, *this, ec); + break; + } + case jsoncons::bson::bson_type::binary_type: + { + uint8_t buf[sizeof(int32_t)]; + std::size_t n = source_.read(buf, sizeof(int32_t)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(int32_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + const auto len = binary::little_to_native(buf, sizeof(buf)); + if (JSONCONS_UNLIKELY(len < 0)) + { + ec = bson_errc::length_is_negative; + more_ = false; + return; + } + uint8_t subtype; + n = source_.read(&subtype, 1); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != 1)) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + + bytes_buffer_.clear(); + n = source_reader::read(source_, bytes_buffer_, len); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != static_cast(len))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + + more_ = visitor.byte_string_value(bytes_buffer_, + subtype, + *this, + ec); + break; + } + case jsoncons::bson::bson_type::decimal128_type: + { + uint8_t buf[sizeof(uint64_t)*2]; + std::size_t n = source_.read(buf, sizeof(buf)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(buf))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + + decimal128_t dec; + dec.low = binary::little_to_native(buf, sizeof(uint64_t)); + dec.high = binary::little_to_native(buf+sizeof(uint64_t), sizeof(uint64_t)); + + text_buffer_.clear(); + text_buffer_.resize(bson::decimal128_limits::buf_size); + auto r = bson::decimal128_to_chars(&text_buffer_[0], &text_buffer_[0]+text_buffer_.size(), dec); + more_ = visitor.string_value(string_view(text_buffer_.data(),static_cast(r.ptr-text_buffer_.data())), semantic_tag::float128, *this, ec); + break; + } + case jsoncons::bson::bson_type::object_id_type: + { + uint8_t buf[12]; + std::size_t n = source_.read(buf, sizeof(buf)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(buf))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + + oid_t oid(buf); + to_string(oid, text_buffer_); + + more_ = visitor.string_value(text_buffer_, semantic_tag::id, *this, ec); + break; + } + default: + { + ec = bson_errc::unknown_type; + more_ = false; + return; + } + } + } + + void read_cstring(string_type& buffer, std::error_code& ec) + { + uint8_t c = 0xff; + while (true) + { + std::size_t n = source_.read(&c, 1); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != 1)) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + if (c == 0) + { + break; + } + buffer.push_back(c); + } + } + + void read_string(string_type& buffer, std::error_code& ec) + { + uint8_t buf[sizeof(int32_t)]; + std::size_t n = source_.read(buf, sizeof(int32_t)); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != sizeof(int32_t))) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + auto len = binary::little_to_native(buf, sizeof(buf)); + if (JSONCONS_UNLIKELY(len < 1)) + { + ec = bson_errc::string_length_is_non_positive; + more_ = false; + return; + } + + std::size_t size = static_cast(len) - static_cast(1); + n = source_reader::read(source_, buffer, size); + state_stack_.back().pos += n; + + if (JSONCONS_UNLIKELY(n != size)) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + uint8_t c; + n = source_.read(&c, 1); + state_stack_.back().pos += n; + if (JSONCONS_UNLIKELY(n != 1)) + { + ec = bson_errc::unexpected_eof; + more_ = false; + return; + } + } +}; + +}} + +#endif diff --git a/third_party/jsoncons_ext/bson/bson_reader.hpp b/third_party/jsoncons_ext/bson/bson_reader.hpp new file mode 100644 index 0000000000..a2416ceda3 --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_reader.hpp @@ -0,0 +1,87 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_READER_HPP +#define JSONCONS_BSON_BSON_READER_HPP + +#include +#include +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace bson { + +template > +class basic_bson_reader +{ + basic_bson_parser parser_; + json_visitor& visitor_; +public: + template + basic_bson_reader(Sourceable&& source, + json_visitor& visitor, + const TempAllocator& temp_alloc) + : basic_bson_reader(std::forward(source), + visitor, + bson_decode_options(), + temp_alloc) + { + } + + template + basic_bson_reader(Sourceable&& source, + json_visitor& visitor, + const bson_decode_options& options = bson_decode_options(), + const TempAllocator& temp_alloc=TempAllocator()) + : parser_(std::forward(source), options, temp_alloc), + visitor_(visitor) + { + } + + void read() + { + std::error_code ec; + read(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line(),column())); + } + } + + void read(std::error_code& ec) + { + parser_.reset(); + parser_.parse(visitor_, ec); + if (ec) + { + return; + } + } + + std::size_t line() const + { + return parser_.line(); + } + + std::size_t column() const + { + return parser_.column(); + } +}; + +using bson_stream_reader = basic_bson_reader; +using bson_bytes_reader = basic_bson_reader; + +}} + +#endif diff --git a/third_party/jsoncons_ext/bson/bson_type.hpp b/third_party/jsoncons_ext/bson/bson_type.hpp new file mode 100644 index 0000000000..e049967f5e --- /dev/null +++ b/third_party/jsoncons_ext/bson/bson_type.hpp @@ -0,0 +1,44 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_BSON_TYPE_HPP +#define JSONCONS_BSON_BSON_TYPE_HPP + +#include +#include +#include + +namespace jsoncons { namespace bson { + + namespace bson_type + { + const uint8_t double_type = 0x01; + const uint8_t string_type = 0x02; // UTF-8 string + const uint8_t document_type = 0x03; + const uint8_t array_type = 0x04; + const uint8_t binary_type = 0x05; + const uint8_t undefined_type = 0x06; // map to null + const uint8_t object_id_type = 0x07; + const uint8_t bool_type = 0x08; + const uint8_t datetime_type = 0x09; + const uint8_t null_type = 0x0a; + const uint8_t regex_type = 0x0b; + const uint8_t javascript_type = 0x0d; + const uint8_t symbol_type = 0x0e; // deprecated, mapped to string + const uint8_t javascript_with_scope_type = 0x0f; // unsupported + const uint8_t int32_type = 0x10; + const uint8_t timestamp_type = 0x11; // MongoDB internal Timestamp, uint64 + const uint8_t int64_type = 0x12; + const uint8_t decimal128_type = 0x13; + const uint8_t min_key_type = 0xff; + const uint8_t max_key_type = 0x7f; + } + + enum class bson_container_type {document, array}; + +}} + +#endif diff --git a/third_party/jsoncons_ext/bson/decode_bson.hpp b/third_party/jsoncons_ext/bson/decode_bson.hpp new file mode 100644 index 0000000000..7bf9066722 --- /dev/null +++ b/third_party/jsoncons_ext/bson/decode_bson.hpp @@ -0,0 +1,202 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_DECODE_BSON_HPP +#define JSONCONS_BSON_DECODE_BSON_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace bson { + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_bson(const Source& v, + const bson_decode_options& options = bson_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_bson_reader reader(v, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_bson(const Source& v, + const bson_decode_options& options = bson_decode_options()) + { + basic_bson_cursor cursor(v, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_bson(std::istream& is, + const bson_decode_options& options = bson_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + bson_stream_reader reader(is, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_bson(std::istream& is, + const bson_decode_options& options = bson_decode_options()) + { + basic_bson_cursor cursor(is, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_bson(InputIt first, InputIt last, + const bson_decode_options& options = bson_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_bson_reader> reader(binary_iterator_source(first, last), adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_bson(InputIt first, InputIt last, + const bson_decode_options& options = bson_decode_options()) + { + basic_bson_cursor> cursor(binary_iterator_source(first, last), options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + // With leading allocator_set parameter + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_bson(const allocator_set& alloc_set, + const Source& v, + const bson_decode_options& options = bson_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_bson_reader reader(v, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_bson(const allocator_set& alloc_set, + const Source& v, + const bson_decode_options& options = bson_decode_options()) + { + basic_bson_cursor cursor(v, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_bson(const allocator_set& alloc_set, + std::istream& is, + const bson_decode_options& options = bson_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_bson_reader reader(is, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_bson(const allocator_set& alloc_set, + std::istream& is, + const bson_decode_options& options = bson_decode_options()) + { + basic_bson_cursor cursor(is, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + +} // bson +} // jsoncons + +#endif diff --git a/third_party/jsoncons_ext/bson/encode_bson.hpp b/third_party/jsoncons_ext/bson/encode_bson.hpp new file mode 100644 index 0000000000..7eacaddc2e --- /dev/null +++ b/third_party/jsoncons_ext/bson/encode_bson.hpp @@ -0,0 +1,144 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_BSON_ENCODE_BSON_HPP +#define JSONCONS_BSON_ENCODE_BSON_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include + +namespace jsoncons { +namespace bson { + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_bson(const T& j, + ByteContainer& cont, + const bson_encode_options& options = bson_encode_options()) + { + using char_type = typename T::char_type; + basic_bson_encoder> encoder(cont, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_bson(const T& val, + ByteContainer& cont, + const bson_encode_options& options = bson_encode_options()) + { + basic_bson_encoder> encoder(cont, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_bson(const T& j, + std::ostream& os, + const bson_encode_options& options = bson_encode_options()) + { + using char_type = typename T::char_type; + bson_stream_encoder encoder(os, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_bson(const T& val, + std::ostream& os, + const bson_encode_options& options = bson_encode_options()) + { + bson_stream_encoder encoder(os, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // with temp_allocator_rag + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_bson(const allocator_set& alloc_set, + const T& j, + ByteContainer& cont, + const bson_encode_options& options = bson_encode_options()) + { + using char_type = typename T::char_type; + basic_bson_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_bson(const allocator_set& alloc_set, + const T& val, + ByteContainer& cont, + const bson_encode_options& options = bson_encode_options()) + { + basic_bson_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_bson(const allocator_set& alloc_set, + const T& j, + std::ostream& os, + const bson_encode_options& options = bson_encode_options()) + { + using char_type = typename T::char_type; + basic_bson_encoder encoder(os, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_bson(const allocator_set& alloc_set, + const T& val, + std::ostream& os, + const bson_encode_options& options = bson_encode_options()) + { + basic_bson_encoder encoder(os, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + +} // bson +} // jsoncons + +#endif diff --git a/third_party/jsoncons_ext/cbor/cbor.hpp b/third_party/jsoncons_ext/cbor/cbor.hpp new file mode 100644 index 0000000000..97efe73348 --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor.hpp @@ -0,0 +1,26 @@ +// Copyright 2017-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + + +#ifndef JSONCONS_CBOR_CBOR_HPP +#define JSONCONS_CBOR_CBOR_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/third_party/jsoncons_ext/cbor/cbor_cursor.hpp b/third_party/jsoncons_ext/cbor/cbor_cursor.hpp new file mode 100644 index 0000000000..9b22bce305 --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_cursor.hpp @@ -0,0 +1,292 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_CBOR_CURSOR_HPP +#define JSONCONS_CBOR_CBOR_CURSOR_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace cbor { + +template > +class basic_cbor_cursor : public basic_staj_cursor, private virtual ser_context +{ +public: + using source_type = Source; + using char_type = char; + using allocator_type = Allocator; +private: + basic_cbor_parser parser_; + basic_staj_visitor cursor_visitor_; + basic_item_event_visitor_to_json_visitor cursor_handler_adaptor_; + bool eof_; + + // Noncopyable and nonmoveable + basic_cbor_cursor(const basic_cbor_cursor&) = delete; + basic_cbor_cursor& operator=(const basic_cbor_cursor&) = delete; + +public: + using string_view_type = string_view; + + template + basic_cbor_cursor(Sourceable&& source, + const cbor_decode_options& options = cbor_decode_options(), + const Allocator& alloc = Allocator()) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + cursor_handler_adaptor_(cursor_visitor_, alloc), + eof_(false) + { + if (!done()) + { + next(); + } + } + + // Constructors that set parse error codes + + template + basic_cbor_cursor(Sourceable&& source, + std::error_code& ec) + : basic_cbor_cursor(std::allocator_arg, Allocator(), + std::forward(source), + cbor_decode_options(), + ec) + { + } + + template + basic_cbor_cursor(Sourceable&& source, + const cbor_decode_options& options, + std::error_code& ec) + : basic_cbor_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + ec) + { + } + + template + basic_cbor_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const cbor_decode_options& options, + std::error_code& ec) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + cursor_handler_adaptor_(cursor_visitor_, alloc), + eof_(false) + { + if (!done()) + { + next(ec); + } + } + + void reset() + { + parser_.reset(); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + template + void reset(Sourceable&& source) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + void reset(std::error_code& ec) + { + parser_.reset(); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + template + void reset(Sourceable&& source, std::error_code& ec) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + bool done() const override + { + return parser_.done(); + } + + bool is_typed_array() const + { + return cursor_visitor_.is_typed_array(); + } + + const staj_event& current() const override + { + return cursor_visitor_.event(); + } + + void read_to(basic_json_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_json_visitor& visitor, + std::error_code& ec) override + { + if (cursor_visitor_.dump(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + const ser_context& context() const override + { + return *this; + } + + bool eof() const + { + return eof_; + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + staj_filter_view operator|(basic_cbor_cursor& cursor, + std::function pred) + { + return staj_filter_view(cursor, pred); + } + +private: + static bool accept_all(const staj_event&, const ser_context&) + { + return true; + } + + void read_next(std::error_code& ec) + { + if (cursor_visitor_.in_available()) + { + cursor_visitor_.send_available(ec); + } + else + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(cursor_handler_adaptor_, ec); + if (ec) return; + } + } + } + + void read_next(basic_json_visitor& visitor, std::error_code& ec) + { + { + struct resource_wrapper + { + basic_item_event_visitor_to_json_visitor& adaptor; + basic_json_visitor& original; + + resource_wrapper(basic_item_event_visitor_to_json_visitor& adaptor, + basic_json_visitor& visitor) + : adaptor(adaptor), original(adaptor.destination()) + { + adaptor.destination(visitor); + } + + ~resource_wrapper() + { + adaptor.destination(original); + } + } wrapper(cursor_handler_adaptor_, visitor); + + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(cursor_handler_adaptor_, ec); + if (ec) + { + return; + } + } + } + } +}; + +using cbor_stream_cursor = basic_cbor_cursor; +using cbor_bytes_cursor = basic_cbor_cursor; + +} // namespace cbor +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons_ext/cbor/cbor_detail.hpp b/third_party/jsoncons_ext/cbor/cbor_detail.hpp new file mode 100644 index 0000000000..5b508a079a --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_detail.hpp @@ -0,0 +1,93 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_CBOR_DETAIL_HPP +#define JSONCONS_CBOR_CBOR_DETAIL_HPP + +#include +#include +#include +#include // std::forward_iterator_tag +#include // std::numeric_limits +#include // std::move +#include +#include +#include + +// 0x00..0x17 (0..23) +#define JSONCONS_CBOR_0x00_0x17 \ + 0x00:case 0x01:case 0x02:case 0x03:case 0x04:case 0x05:case 0x06:case 0x07:case 0x08:case 0x09:case 0x0a:case 0x0b:case 0x0c:case 0x0d:case 0x0e:case 0x0f:case 0x10:case 0x11:case 0x12:case 0x13:case 0x14:case 0x15:case 0x16:case 0x17 + +#define JSONCONS_CBOR_ARRAY_TAGS \ + 0x40:case 0x41:case 0x42:case 0x43:case 0x44:case 0x45:case 0x46:case 0x47:case 0x48:case 0x49:case 0x4a:case 0x4b:case 0x4c:case 0x4d:case 0x4e:case 0x4f:case 0x50:case 0x51:case 0x52:case 0x53:case 0x54:case 0x55:case 0x56:case 0x57 + +namespace jsoncons { namespace cbor { namespace detail { + +//const uint8_t cbor_array_tags_010_mask = 0b11100000; +//const uint8_t cbor_array_tags_f_mask = 0b00010000; +//const uint8_t cbor_array_tags_s_mask = 0b00001000; +//const uint8_t cbor_array_tags_e_mask = 0b00000100; +//const uint8_t cbor_array_tags_ll_mask = 0b00000011; + +const uint8_t cbor_array_tags_010_mask = 0xE0; +const uint8_t cbor_array_tags_f_mask = 0x10; +const uint8_t cbor_array_tags_s_mask = 0x08; +const uint8_t cbor_array_tags_e_mask = 0x04; +const uint8_t cbor_array_tags_ll_mask = 0x03; + +const uint8_t cbor_array_tags_010_shift = 5; +const uint8_t cbor_array_tags_f_shift = 4; +const uint8_t cbor_array_tags_s_shift = 3; +const uint8_t cbor_array_tags_e_shift = 2; +const uint8_t cbor_array_tags_ll_shift = 0; + +enum class cbor_major_type : uint8_t +{ + unsigned_integer = 0x00, + negative_integer = 0x01, + byte_string = 0x02, + text_string = 0x03, + array = 0x04, + map = 0x05, + semantic_tag = 0x06, + simple = 0x7 +}; + +namespace additional_info +{ + const uint8_t indefinite_length = 0x1f; +} + +inline +size_t min_length_for_stringref(uint64_t index) +{ + std::size_t n; + if (index <= 23) + { + n = 3; + } + else if (index <= 255) + { + n = 4; + } + else if (index <= 65535) + { + n = 5; + } + else if (index <= 4294967295) + { + n = 7; + } + else + { + n = 11; + } + return n; +} + +}}} + +#endif diff --git a/third_party/jsoncons_ext/cbor/cbor_encoder.hpp b/third_party/jsoncons_ext/cbor/cbor_encoder.hpp new file mode 100644 index 0000000000..ee613d83a7 --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_encoder.hpp @@ -0,0 +1,1744 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_CBOR_ENCODER_HPP +#define JSONCONS_CBOR_CBOR_ENCODER_HPP + +#include +#include +#include // std::numeric_limits +#include +#include // std::move +#include // jsoncons::ser_error +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace cbor { + +enum class cbor_container_type {object, indefinite_length_object, array, indefinite_length_array}; + +template > +class basic_cbor_encoder final : public basic_json_visitor +{ + using super_type = basic_json_visitor; + + enum class decimal_parse_state { start, integer, exp1, exp2, fraction1 }; + enum class hexfloat_parse_state { start, expect_0, expect_x, integer, exp1, exp2, fraction1 }; + + static constexpr int64_t nanos_in_second = 1000000000; + static constexpr int64_t millis_in_second = 1000; + +public: + using allocator_type = Allocator; + using sink_type = Sink; + using typename super_type::char_type; + using typename super_type::string_view_type; + +private: + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + using string_type = std::basic_string,char_allocator_type>; + using byte_string_type = basic_byte_string; + + struct stack_item + { + cbor_container_type type_; + std::size_t length_; + std::size_t count_; + + stack_item(cbor_container_type type, std::size_t length = 0) noexcept + : type_(type), length_(length), count_(0) + { + } + + std::size_t length() const + { + return length_; + } + + std::size_t count() const + { + return count_; + } + + bool is_object() const + { + return type_ == cbor_container_type::object || type_ == cbor_container_type::indefinite_length_object; + } + + bool is_indefinite_length() const + { + return type_ == cbor_container_type::indefinite_length_array || type_ == cbor_container_type::indefinite_length_object; + } + + }; + + using string_size_allocator_type = typename std::allocator_traits:: template rebind_alloc>; + using byte_string_size_allocator_type = typename std::allocator_traits:: template rebind_alloc>; + using stack_item_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + Sink sink_; + const cbor_encode_options options_; + allocator_type alloc_; + + std::vector stack_; + std::map,string_size_allocator_type> stringref_map_; + std::map,byte_string_size_allocator_type> bytestringref_map_; + std::size_t next_stringref_ = 0; + int nesting_depth_; + + // Noncopyable and nonmoveable + basic_cbor_encoder(const basic_cbor_encoder&) = delete; + basic_cbor_encoder& operator=(const basic_cbor_encoder&) = delete; +public: + explicit basic_cbor_encoder(Sink&& sink, + const Allocator& alloc = Allocator()) + : basic_cbor_encoder(std::forward(sink), cbor_encode_options(), alloc) + { + } + basic_cbor_encoder(Sink&& sink, + const cbor_encode_options& options, + const Allocator& alloc = Allocator()) + : sink_(std::forward(sink)), + options_(options), + alloc_(alloc), + stack_(alloc), + stringref_map_(alloc), + bytestringref_map_(alloc), + nesting_depth_(0) + { + if (options.pack_strings()) + { + write_tag(256); + } + } + + ~basic_cbor_encoder() noexcept + { + JSONCONS_TRY + { + sink_.flush(); + } + JSONCONS_CATCH(...) + { + } + } + + void reset() + { + stack_.clear(); + stringref_map_.clear(); + bytestringref_map_.clear(); + next_stringref_ = 0; + nesting_depth_ = 0; + } + + void reset(Sink&& sink) + { + sink_ = std::move(sink); + reset(); + } + +private: + // Implementing methods + + void visit_flush() override + { + sink_.flush(); + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = cbor_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(cbor_container_type::indefinite_length_object); + + sink_.push_back(0xbf); + return true; + } + + bool visit_begin_object(std::size_t length, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = cbor_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(cbor_container_type::object, length); + + if (length <= 0x17) + { + binary::native_to_big(static_cast(0xa0 + length), + std::back_inserter(sink_)); + } + else if (length <= 0xff) + { + binary::native_to_big(static_cast(0xb8), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffff) + { + binary::native_to_big(static_cast(0xb9), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffffffff) + { + binary::native_to_big(static_cast(0xba), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (uint64_t(length) <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0xbb), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + + return true; + } + + bool visit_end_object(const ser_context&, std::error_code& ec) override + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + if (stack_.back().is_indefinite_length()) + { + sink_.push_back(0xff); + } + else + { + if (stack_.back().count() < stack_.back().length()) + { + ec = cbor_errc::too_few_items; + return false; + } + if (stack_.back().count() > stack_.back().length()) + { + ec = cbor_errc::too_many_items; + return false; + } + } + + stack_.pop_back(); + end_value(); + + return true; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = cbor_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(cbor_container_type::indefinite_length_array); + sink_.push_back(0x9f); + return true; + } + + bool visit_begin_array(std::size_t length, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = cbor_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(cbor_container_type::array, length); + if (length <= 0x17) + { + binary::native_to_big(static_cast(0x80 + length), + std::back_inserter(sink_)); + } + else if (length <= 0xff) + { + binary::native_to_big(static_cast(0x98), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffff) + { + binary::native_to_big(static_cast(0x99), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffffffff) + { + binary::native_to_big(static_cast(0x9a), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (uint64_t(length) <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x9b), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + return true; + } + + bool visit_end_array(const ser_context&, std::error_code& ec) override + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + if (stack_.back().is_indefinite_length()) + { + sink_.push_back(0xff); + } + else + { + if (stack_.back().count() < stack_.back().length()) + { + ec = cbor_errc::too_few_items; + return false; + } + if (stack_.back().count() > stack_.back().length()) + { + ec = cbor_errc::too_many_items; + return false; + } + } + + stack_.pop_back(); + end_value(); + + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) override + { + write_string(name); + return true; + } + + bool visit_null(semantic_tag tag, const ser_context&, std::error_code&) override + { + if (tag == semantic_tag::undefined) + { + sink_.push_back(0xf7); + } + else + { + sink_.push_back(0xf6); + } + + end_value(); + return true; + } + + void write_string(const string_view& sv) + { + auto sink = unicode_traits::validate(sv.data(), sv.size()); + if (sink.ec != unicode_traits::conv_errc()) + { + JSONCONS_THROW(ser_error(cbor_errc::invalid_utf8_text_string)); + } + + if (options_.pack_strings() && sv.size() >= jsoncons::cbor::detail::min_length_for_stringref(next_stringref_)) + { + string_type s(sv.data(), sv.size(), alloc_); + auto it = stringref_map_.find(s); + if (it == stringref_map_.end()) + { + stringref_map_.emplace(std::make_pair(std::move(s), next_stringref_++)); + write_utf8_string(sv); + } + else + { + write_tag(25); + write_uint64_value(it->second); + } + } + else + { + write_utf8_string(sv); + } + } + + void write_utf8_string(const string_view& sv) + { + const size_t length = sv.size(); + + if (length <= 0x17) + { + // fixstr stores a byte array whose length is upto 31 bytes + binary::native_to_big(static_cast(0x60 + length), + std::back_inserter(sink_)); + } + else if (length <= 0xff) + { + binary::native_to_big(static_cast(0x78), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffff) + { + binary::native_to_big(static_cast(0x79), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffffffff) + { + binary::native_to_big(static_cast(0x7a), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (uint64_t(length) <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x7b), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + + for (auto c : sv) + { + sink_.push_back(c); + } + } + + void write_bignum(bigint& n) + { + bool is_neg = n < 0; + if (is_neg) + { + n = - n -1; + } + + int signum; + std::vector data; + n.write_bytes_be(signum, data); + std::size_t length = data.size(); + + if (is_neg) + { + write_tag(3); + } + else + { + write_tag(2); + } + + if (length <= 0x17) + { + // fixstr stores a byte array whose length is upto 31 bytes + binary::native_to_big(static_cast(0x40 + length), + std::back_inserter(sink_)); + } + else if (length <= 0xff) + { + binary::native_to_big(static_cast(0x58), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffff) + { + binary::native_to_big(static_cast(0x59), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= 0xffffffff) + { + binary::native_to_big(static_cast(0x5a), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (uint64_t(length) <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x5b), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + + for (auto c : data) + { + sink_.push_back(c); + } + } + + bool write_decimal_value(const string_view_type& sv, const ser_context& context, std::error_code& ec) + { + bool more = true; + + decimal_parse_state state = decimal_parse_state::start; + std::basic_string s; + std::basic_string exponent; + int64_t scale = 0; + for (auto c : sv) + { + switch (state) + { + case decimal_parse_state::start: + { + switch (c) + { + case '-': + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + s.push_back(c); + state = decimal_parse_state::integer; + break; + default: + { + ec = cbor_errc::invalid_decimal_fraction; + return false; + } + } + break; + } + case decimal_parse_state::integer: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + s.push_back(c); + break; + case 'e': case 'E': + state = decimal_parse_state::exp1; + break; + case '.': + state = decimal_parse_state::fraction1; + break; + default: + { + ec = cbor_errc::invalid_decimal_fraction; + return false; + } + } + break; + } + case decimal_parse_state::exp1: + { + switch (c) + { + case '+': + state = decimal_parse_state::exp2; + break; + case '-': + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + exponent.push_back(c); + state = decimal_parse_state::exp2; + break; + default: + { + ec = cbor_errc::invalid_decimal_fraction; + return false; + } + } + break; + } + case decimal_parse_state::exp2: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + exponent.push_back(c); + break; + default: + { + ec = cbor_errc::invalid_decimal_fraction; + return false; + } + } + break; + } + case decimal_parse_state::fraction1: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + s.push_back(c); + --scale; + break; + default: + { + ec = cbor_errc::invalid_decimal_fraction; + return false; + } + } + break; + } + } + } + + write_tag(4); + more = visit_begin_array((std::size_t)2, semantic_tag::none, context, ec); + if (!more) {return more;} + if (exponent.length() > 0) + { + int64_t val; + auto r = jsoncons::detail::to_integer(exponent.data(), exponent.length(), val); + if (!r) + { + ec = r.error_code(); + return false; + } + scale += val; + } + more = visit_int64(scale, semantic_tag::none, context, ec); + if (!more) {return more;} + + int64_t val{ 0 }; + auto r = jsoncons::detail::to_integer(s.data(),s.length(), val); + if (r) + { + more = visit_int64(val, semantic_tag::none, context, ec); + if (!more) {return more;} + } + else if (r.error_code() == jsoncons::detail::to_integer_errc::overflow) + { + bigint n = bigint::from_string(s.data(), s.length()); + write_bignum(n); + end_value(); + } + else + { + ec = r.error_code(); + return false; + } + more = visit_end_array(context, ec); + + return more; + } + + bool write_hexfloat_value(const string_view_type& sv, const ser_context& context, std::error_code& ec) + { + bool more = true; + + hexfloat_parse_state state = hexfloat_parse_state::start; + std::basic_string s; + std::basic_string exponent; + int64_t scale = 0; + + for (auto c : sv) + { + switch (state) + { + case hexfloat_parse_state::start: + { + switch (c) + { + case '-': + s.push_back(c); + state = hexfloat_parse_state::expect_0; + break; + case '0': + state = hexfloat_parse_state::expect_x; + break; + default: + { + ec = cbor_errc::invalid_bigfloat; + return false; + } + } + break; + } + case hexfloat_parse_state::expect_0: + { + switch (c) + { + case '0': + state = hexfloat_parse_state::expect_x; + break; + default: + { + ec = cbor_errc::invalid_bigfloat; + return false; + } + } + break; + } + case hexfloat_parse_state::expect_x: + { + switch (c) + { + case 'x': + case 'X': + state = hexfloat_parse_state::integer; + break; + default: + { + ec = cbor_errc::invalid_bigfloat; + return false; + } + } + break; + } + case hexfloat_parse_state::integer: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + s.push_back(c); + break; + case 'p': case 'P': + state = hexfloat_parse_state::exp1; + break; + case '.': + state = hexfloat_parse_state::fraction1; + break; + default: + { + ec = cbor_errc::invalid_bigfloat; + return false; + } + } + break; + } + case hexfloat_parse_state::exp1: + { + switch (c) + { + case '+': + state = hexfloat_parse_state::exp2; + break; + case '-': + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + exponent.push_back(c); + state = hexfloat_parse_state::exp2; + break; + default: + { + ec = cbor_errc::invalid_bigfloat; + return false; + } + } + break; + } + case hexfloat_parse_state::exp2: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + exponent.push_back(c); + break; + default: + { + ec = cbor_errc::invalid_bigfloat; + return false; + } + } + break; + } + case hexfloat_parse_state::fraction1: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9':case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + s.push_back(c); + scale -= 4; + break; + default: + { + ec = cbor_errc::invalid_bigfloat; + return false; + } + } + break; + } + } + } + + write_tag(5); + more = visit_begin_array((std::size_t)2, semantic_tag::none, context, ec); + if (!more) return more; + + if (exponent.length() > 0) + { + int64_t val{ 0 }; + auto r = jsoncons::detail::hex_to_integer(exponent.data(), exponent.length(), val); + if (!r) + { + ec = r.error_code(); + return false; + } + scale += val; + } + more = visit_int64(scale, semantic_tag::none, context, ec); + if (!more) return more; + + int64_t val{ 0 }; + auto r = jsoncons::detail::hex_to_integer(s.data(),s.length(), val); + if (r) + { + more = visit_int64(val, semantic_tag::none, context, ec); + if (!more) return more; + } + else if (r.error_code() == jsoncons::detail::to_integer_errc::overflow) + { + bigint n = bigint::from_string_radix(s.data(), s.length(), 16); + write_bignum(n); + end_value(); + } + else + { + JSONCONS_THROW(json_runtime_error(r.error_code().message())); + } + return visit_end_array(context, ec); + } + + bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context& context, std::error_code& ec) override + { + switch (tag) + { + case semantic_tag::bigint: + { + bigint n = bigint::from_string(sv.data(), sv.length()); + write_bignum(n); + end_value(); + break; + } + case semantic_tag::bigdec: + { + return write_decimal_value(sv, context, ec); + } + case semantic_tag::bigfloat: + { + return write_hexfloat_value(sv, context, ec); + } + case semantic_tag::datetime: + { + write_tag(0); + + write_string(sv); + end_value(); + break; + } + case semantic_tag::uri: + { + write_tag(32); + write_string(sv); + end_value(); + break; + } + case semantic_tag::base64url: + { + write_tag(33); + write_string(sv); + end_value(); + break; + } + case semantic_tag::base64: + { + write_tag(34); + write_string(sv); + end_value(); + break; + } + default: + { + write_string(sv); + end_value(); + break; + } + } + return true; + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + byte_string_chars_format encoding_hint; + switch (tag) + { + case semantic_tag::base16: + encoding_hint = byte_string_chars_format::base16; + break; + case semantic_tag::base64: + encoding_hint = byte_string_chars_format::base64; + break; + case semantic_tag::base64url: + encoding_hint = byte_string_chars_format::base64url; + break; + default: + encoding_hint = byte_string_chars_format::none; + break; + } + switch (encoding_hint) + { + case byte_string_chars_format::base64url: + write_tag(21); + break; + case byte_string_chars_format::base64: + write_tag(22); + break; + case byte_string_chars_format::base16: + write_tag(23); + break; + default: + break; + } + if (options_.pack_strings() && b.size() >= jsoncons::cbor::detail::min_length_for_stringref(next_stringref_)) + { + byte_string_type bs(b.data(), b.size(), alloc_); + auto it = bytestringref_map_.find(bs); + if (it == bytestringref_map_.end()) + { + bytestringref_map_.emplace(std::make_pair(bs, next_stringref_++)); + write_byte_string_value(bs); + } + else + { + write_tag(25); + write_uint64_value(it->second); + } + } + else + { + write_byte_string_value(b); + } + + end_value(); + return true; + } + + bool visit_byte_string(const byte_string_view& b, + uint64_t ext_tag, + const ser_context&, + std::error_code&) override + { + if (options_.pack_strings() && b.size() >= jsoncons::cbor::detail::min_length_for_stringref(next_stringref_)) + { + byte_string_type bs(b.data(), b.size(), alloc_); + auto it = bytestringref_map_.find(bs); + if (it == bytestringref_map_.end()) + { + bytestringref_map_.emplace(std::make_pair(bs, next_stringref_++)); + write_tag(ext_tag); + write_byte_string_value(bs); + } + else + { + write_tag(25); + write_uint64_value(it->second); + } + } + else + { + write_tag(ext_tag); + write_byte_string_value(b); + } + + end_value(); + return true; + } + + void write_byte_string_value(const byte_string_view& b) + { + if (b.size() <= 0x17) + { + // fixstr stores a byte array whose length is upto 31 bytes + binary::native_to_big(static_cast(0x40 + b.size()), + std::back_inserter(sink_)); + } + else if (b.size() <= 0xff) + { + binary::native_to_big(static_cast(0x58), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(b.size()), + std::back_inserter(sink_)); + } + else if (b.size() <= 0xffff) + { + binary::native_to_big(static_cast(0x59), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(b.size()), + std::back_inserter(sink_)); + } + else if (b.size() <= 0xffffffff) + { + binary::native_to_big(static_cast(0x5a), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(b.size()), + std::back_inserter(sink_)); + } + else // if (b.size() <= 0xffffffffffffffff) + { + binary::native_to_big(static_cast(0x5b), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(b.size()), + std::back_inserter(sink_)); + } + + for (auto c : b) + { + sink_.push_back(c); + } + } + + bool visit_double(double val, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + switch (tag) + { + case semantic_tag::epoch_second: + write_tag(1); + break; + case semantic_tag::epoch_milli: + write_tag(1); + if (val != 0) + { + val /= millis_in_second; + } + break; + case semantic_tag::epoch_nano: + write_tag(1); + if (val != 0) + { + val /= nanos_in_second; + } + break; + default: + break; + } + + float valf = (float)val; + if ((double)valf == val) + { + binary::native_to_big(static_cast(0xfa), + std::back_inserter(sink_)); + binary::native_to_big(valf, std::back_inserter(sink_)); + } + else + { + binary::native_to_big(static_cast(0xfb), + std::back_inserter(sink_)); + binary::native_to_big(val, std::back_inserter(sink_)); + } + + // write double + + end_value(); + return true; + } + + bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + switch (tag) + { + case semantic_tag::epoch_milli: + case semantic_tag::epoch_nano: + return visit_double(static_cast(value), tag, context, ec); + case semantic_tag::epoch_second: + write_tag(1); + break; + default: + break; + } + if (value >= 0) + { + if (value <= 0x17) + { + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else if (value <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x18), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else if (value <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x19), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else if (value <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x1a), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else if (value <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x1b), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + } else + { + const auto posnum = -1 - value; + if (value >= -24) + { + binary::native_to_big(static_cast(0x20 + posnum), + std::back_inserter(sink_)); + } + else if (posnum <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x38), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(posnum), + std::back_inserter(sink_)); + } + else if (posnum <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x39), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(posnum), + std::back_inserter(sink_)); + } + else if (posnum <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x3a), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(posnum), + std::back_inserter(sink_)); + } + else if (posnum <= (std::numeric_limits::max)()) + { + binary::native_to_big(static_cast(0x3b), + std::back_inserter(sink_)); + binary::native_to_big(static_cast(posnum), + std::back_inserter(sink_)); + } + } + end_value(); + return true; + } + + bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + switch (tag) + { + case semantic_tag::epoch_milli: + case semantic_tag::epoch_nano: + return visit_double(static_cast(value), tag, context, ec); + case semantic_tag::epoch_second: + write_tag(1); + break; + default: + break; + } + + write_uint64_value(value); + end_value(); + return true; + } + + void write_tag(uint64_t value) + { + if (value <= 0x17) + { + sink_.push_back(0xc0 | static_cast(value)); + } + else if (value <=(std::numeric_limits::max)()) + { + sink_.push_back(0xd8); + sink_.push_back(static_cast(value)); + } + else if (value <=(std::numeric_limits::max)()) + { + sink_.push_back(0xd9); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else if (value <=(std::numeric_limits::max)()) + { + sink_.push_back(0xda); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else + { + sink_.push_back(0xdb); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + } + + void write_uint64_value(uint64_t value) + { + if (value <= 0x17) + { + sink_.push_back(static_cast(value)); + } + else if (value <=(std::numeric_limits::max)()) + { + sink_.push_back(static_cast(0x18)); + sink_.push_back(static_cast(value)); + } + else if (value <=(std::numeric_limits::max)()) + { + sink_.push_back(static_cast(0x19)); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else if (value <=(std::numeric_limits::max)()) + { + sink_.push_back(static_cast(0x1a)); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + else if (value <=(std::numeric_limits::max)()) + { + sink_.push_back(static_cast(0x1b)); + binary::native_to_big(static_cast(value), + std::back_inserter(sink_)); + } + } + + bool visit_bool(bool value, semantic_tag, const ser_context&, std::error_code&) override + { + if (value) + { + sink_.push_back(0xf5); + } + else + { + sink_.push_back(0xf4); + } + + end_value(); + return true; + } + + bool visit_typed_array(const jsoncons::span& v, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + switch (tag) + { + case semantic_tag::clamped: + write_tag(0x44); + break; + default: + write_tag(0x40); + break; + } + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(v.size(), semantic_tag::none, context, ec); + for (auto p = v.begin(); more && p != v.end(); ++p) + { + more = this->uint64_value(*p, tag, context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + uint16_t(), + tag); + std::vector v(data.size()*sizeof(uint16_t)); + std::memcpy(v.data(),data.data(),data.size()*sizeof(uint16_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none, context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->uint64_value(*p, tag, context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + uint32_t(), + tag); + std::vector v(data.size()*sizeof(uint32_t)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(uint32_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none, context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->uint64_value(*p, semantic_tag::none, context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + uint64_t(), + tag); + std::vector v(data.size()*sizeof(uint64_t)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(uint64_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none, context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->uint64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_tag(0x48); + std::vector v(data.size()*sizeof(int8_t)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(int8_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none,context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + int16_t(), + tag); + std::vector v(data.size()*sizeof(int16_t)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(int16_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none,context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + int32_t(), + tag); + std::vector v(data.size()*sizeof(int32_t)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(int32_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none,context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + int64_t(), + tag); + std::vector v(data.size()*sizeof(int64_t)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(int64_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none,context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->int64_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(half_arg_t, const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + half_arg, + tag); + std::vector v(data.size()*sizeof(uint16_t)); + std::memcpy(v.data(),data.data(),data.size()*sizeof(uint16_t)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none, context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->half_value(*p, tag, context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + float(), + tag); + std::vector v(data.size()*sizeof(float)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(float)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none,context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->double_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } + + bool visit_typed_array(const jsoncons::span& data, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + if (options_.use_typed_arrays()) + { + write_typed_array_tag(std::integral_constant(), + double(), + tag); + std::vector v(data.size()*sizeof(double)); + std::memcpy(v.data(), data.data(), data.size()*sizeof(double)); + write_byte_string_value(byte_string_view(v)); + return true; + } + else + { + bool more = this->begin_array(data.size(), semantic_tag::none,context, ec); + for (auto p = data.begin(); more && p != data.end(); ++p) + { + more = this->double_value(*p,semantic_tag::none,context, ec); + } + if (more) + { + more = this->end_array(context, ec); + } + return more; + } + } +/* + bool visit_typed_array(const jsoncons::span&, + semantic_tag, + const ser_context&, + std::error_code&) override + { + return true; + } +*/ + bool visit_begin_multi_dim(const jsoncons::span& shape, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + switch (tag) + { + case semantic_tag::multi_dim_column_major: + write_tag(1040); + break; + default: + write_tag(40); + break; + } + bool more = visit_begin_array(2, semantic_tag::none, context, ec); + if (more) + more = visit_begin_array(shape.size(), semantic_tag::none, context, ec); + for (auto it = shape.begin(); more && it != shape.end(); ++it) + { + more = visit_uint64(*it, semantic_tag::none, context, ec); + } + if (more) + { + more = visit_end_array(context, ec); + } + return more; + } + + bool visit_end_multi_dim(const ser_context& context, + std::error_code& ec) override + { + bool more = visit_end_array(context, ec); + return more; + } + + void write_typed_array_tag(std::true_type, + uint16_t, + semantic_tag) + { + write_tag(0x41); // big endian + } + void write_typed_array_tag(std::false_type, + uint16_t, + semantic_tag) + { + write_tag(0x45); + } + + void write_typed_array_tag(std::true_type, + uint32_t, + semantic_tag) + { + write_tag(0x42); // big endian + } + void write_typed_array_tag(std::false_type, + uint32_t, + semantic_tag) + { + write_tag(0x46); // little endian + } + + void write_typed_array_tag(std::true_type, + uint64_t, + semantic_tag) + { + write_tag(0x43); // big endian + } + void write_typed_array_tag(std::false_type, + uint64_t, + semantic_tag) + { + write_tag(0x47); // little endian + } + + void write_typed_array_tag(std::true_type, + int16_t, + semantic_tag) + { + write_tag(0x49); // big endian + } + void write_typed_array_tag(std::false_type, + int16_t, + semantic_tag) + { + write_tag(0x4d); // little endian + } + + void write_typed_array_tag(std::true_type, + int32_t, + semantic_tag) + { + write_tag(0x4a); // big endian + } + void write_typed_array_tag(std::false_type, + int32_t, + semantic_tag) + { + write_tag(0x4e); // little endian + } + + void write_typed_array_tag(std::true_type, + int64_t, + semantic_tag) + { + write_tag(0x4b); // big endian + } + void write_typed_array_tag(std::false_type, + int64_t, + semantic_tag) + { + write_tag(0x4f); // little endian + } + + void write_typed_array_tag(std::true_type, + half_arg_t, + semantic_tag) + { + write_tag(0x50); + } + void write_typed_array_tag(std::false_type, + half_arg_t, + semantic_tag) + { + write_tag(0x54); + } + + void write_typed_array_tag(std::true_type, + float, + semantic_tag) + { + write_tag(0x51); // big endian + } + void write_typed_array_tag(std::false_type, + float, + semantic_tag) + { + write_tag(0x55); // little endian + } + + void write_typed_array_tag(std::true_type, + double, + semantic_tag) + { + write_tag(0x52); // big endian + } + void write_typed_array_tag(std::false_type, + double, + semantic_tag) + { + write_tag(0x56); // little endian + } + + void end_value() + { + if (!stack_.empty()) + { + ++stack_.back().count_; + } + } +}; + +using cbor_stream_encoder = basic_cbor_encoder; +using cbor_bytes_encoder = basic_cbor_encoder>>; + +}} +#endif diff --git a/third_party/jsoncons_ext/cbor/cbor_error.hpp b/third_party/jsoncons_ext/cbor/cbor_error.hpp new file mode 100644 index 0000000000..e072f493ea --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_error.hpp @@ -0,0 +1,98 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_CBOR_ERROR_HPP +#define JSONCONS_CBOR_CBOR_ERROR_HPP + +#include +#include +#include // jsoncons::ser_error + +namespace jsoncons { namespace cbor { + +enum class cbor_errc +{ + success = 0, + unexpected_eof, + source_error, + invalid_decimal_fraction, + invalid_bigfloat, + invalid_utf8_text_string, + too_many_items, + too_few_items, + number_too_large, + stringref_too_large, + max_nesting_depth_exceeded, + unknown_type, + illegal_chunked_string +}; + +class cbor_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/cbor"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case cbor_errc::unexpected_eof: + return "Unexpected end of file"; + case cbor_errc::source_error: + return "Source error"; + case cbor_errc::invalid_decimal_fraction: + return "Invalid decimal fraction"; + case cbor_errc::invalid_bigfloat: + return "Invalid bigfloat"; + case cbor_errc::invalid_utf8_text_string: + return "Illegal UTF-8 encoding in text string"; + case cbor_errc::too_many_items: + return "Too many items were added to a CBOR map or array of known length"; + case cbor_errc::too_few_items: + return "Too few items were added to a CBOR map or array of known length"; + case cbor_errc::number_too_large: + return "Number exceeds implementation limits"; + case cbor_errc::stringref_too_large: + return "stringref exceeds stringref map size"; + case cbor_errc::max_nesting_depth_exceeded: + return "Data item nesting exceeds limit in options"; + case cbor_errc::unknown_type: + return "An unknown type was found in the stream"; + case cbor_errc::illegal_chunked_string: + return "An illegal type was found while parsing an indefinite length string"; + default: + return "Unknown CBOR parser error"; + } + } +}; + +inline +const std::error_category& cbor_error_category() +{ + static cbor_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(cbor_errc e) +{ + return std::error_code(static_cast(e),cbor_error_category()); +} + + +}} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons_ext/cbor/cbor_event_reader.hpp b/third_party/jsoncons_ext/cbor/cbor_event_reader.hpp new file mode 100644 index 0000000000..0fc166b506 --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_event_reader.hpp @@ -0,0 +1,262 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_EVENT_READER_HPP +#define JSONCONS_CBOR_EVENT_READER_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace cbor { + + template > + class cbor_event_reader : public basic_staj_event_reader, private virtual ser_context + { + public: + using source_type = Source; + using char_type = char; + using allocator_type = Allocator; + private: + basic_cbor_parser parser_; + basic_item_event_receiver event_receiver_; + bool eof_; + + // Noncopyable and nonmoveable + cbor_event_reader(const cbor_event_reader&) = delete; + cbor_event_reader& operator=(const cbor_event_reader&) = delete; + + public: + using string_view_type = string_view; + + template + cbor_event_reader(Sourceable&& source, + const cbor_decode_options& options = cbor_decode_options(), + const Allocator& alloc = Allocator()) + : parser_(std::forward(source), options, alloc), + event_receiver_(accept_all), + eof_(false) + { + if (!done()) + { + next(); + } + } + + // Constructors that set parse error codes + + template + cbor_event_reader(Sourceable&& source, + std::error_code& ec) + : cbor_event_reader(std::allocator_arg, Allocator(), + std::forward(source), + cbor_decode_options(), + ec) + { + } + + template + cbor_event_reader(Sourceable&& source, + const cbor_decode_options& options, + std::error_code& ec) + : cbor_event_reader(std::allocator_arg, Allocator(), + std::forward(source), + options, + ec) + { + } + + template + cbor_event_reader(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const cbor_decode_options& options, + std::error_code& ec) + : parser_(std::forward(source), options, alloc), + event_receiver_(accept_all), + eof_(false) + { + if (!done()) + { + next(ec); + } + } + + void reset() + { + parser_.reset(); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + template + void reset(Sourceable&& source) + { + parser_.reset(std::forward(source)); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + void reset(std::error_code& ec) + { + parser_.reset(); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + template + void reset(Sourceable&& source, std::error_code& ec) + { + parser_.reset(std::forward(source)); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + bool done() const override + { + return parser_.done(); + } + + bool is_typed_array() const + { + return event_receiver_.is_typed_array(); + } + + const basic_staj_event& current() const override + { + return event_receiver_.event(); + } + + void read_to(basic_item_event_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_item_event_visitor& visitor, + std::error_code& ec) override + { + if (event_receiver_.dump(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + const ser_context& context() const override + { + return *this; + } + + bool eof() const + { + return eof_; + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + staj2_filter_view operator|(cbor_event_reader& cursor, + std::function pred) + { + return staj2_filter_view(cursor, pred); + } + + private: + static bool accept_all(const item_event&, const ser_context&) + { + return true; + } + + void read_next(std::error_code& ec) + { + if (event_receiver_.in_available()) + { + event_receiver_.send_available(ec); + } + else + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(event_receiver_, ec); + if (ec) return; + } + } + } + + void read_next(basic_item_event_visitor& visitor, std::error_code& ec) + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(visitor, ec); + if (ec) + { + return; + } + } + } + }; + +} // namespace cbor +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons_ext/cbor/cbor_options.hpp b/third_party/jsoncons_ext/cbor/cbor_options.hpp new file mode 100644 index 0000000000..290aec87ed --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_options.hpp @@ -0,0 +1,104 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_CBOR_OPTIONS_HPP +#define JSONCONS_CBOR_CBOR_OPTIONS_HPP + +#include +#include // std::numeric_limits +#include +#include +#include + +namespace jsoncons { namespace cbor { + +class cbor_options; + +class cbor_options_common +{ + friend class cbor_options; + + int max_nesting_depth_; +protected: + virtual ~cbor_options_common() = default; + + cbor_options_common() + : max_nesting_depth_(1024) + { + } + + cbor_options_common(const cbor_options_common&) = default; + cbor_options_common& operator=(const cbor_options_common&) = default; + cbor_options_common(cbor_options_common&&) = default; + cbor_options_common& operator=(cbor_options_common&&) = default; +public: + int max_nesting_depth() const + { + return max_nesting_depth_; + } +}; + +class cbor_decode_options : public virtual cbor_options_common +{ + friend class cbor_options; +public: + cbor_decode_options() + { + } +}; + +class cbor_encode_options : public virtual cbor_options_common +{ + friend class cbor_options; + + bool use_stringref_; + bool use_typed_arrays_; +public: + cbor_encode_options() + : use_stringref_(false), + use_typed_arrays_(false) + { + } + + bool pack_strings() const + { + return use_stringref_; + } + + bool use_typed_arrays() const + { + return use_typed_arrays_; + } +}; + +class cbor_options final : public cbor_decode_options, public cbor_encode_options +{ +public: + using cbor_options_common::max_nesting_depth; + using cbor_encode_options::pack_strings; + using cbor_encode_options::use_typed_arrays; + + cbor_options& max_nesting_depth(int value) + { + this->max_nesting_depth_ = value; + return *this; + } + + cbor_options& pack_strings(bool value) + { + this->use_stringref_ = value; + return *this; + } + + cbor_options& use_typed_arrays(bool value) + { + this->use_typed_arrays_ = value; + return *this; + } +}; + +}} +#endif diff --git a/third_party/jsoncons_ext/cbor/cbor_parser.hpp b/third_party/jsoncons_ext/cbor/cbor_parser.hpp new file mode 100644 index 0000000000..a77687acb4 --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_parser.hpp @@ -0,0 +1,1962 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_CBOR_PARSER_HPP +#define JSONCONS_CBOR_CBOR_PARSER_HPP + +#include +#include +#include +#include // std::move +#include // std::bitset +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace cbor { + +enum class parse_mode {root,accept,array,indefinite_array,map_key,map_value,indefinite_map_key,indefinite_map_value,multi_dim}; + +struct parse_state +{ + parse_mode mode; + std::size_t length; + std::size_t index; + bool pop_stringref_map_stack; + + parse_state(parse_mode mode, std::size_t length, bool pop_stringref_map_stack = false) noexcept + : mode(mode), length(length), index(0), pop_stringref_map_stack(pop_stringref_map_stack) + { + } + + parse_state(const parse_state&) = default; + parse_state(parse_state&&) = default; +}; + +template > +class basic_cbor_parser : public ser_context +{ + using char_type = char; + using char_traits_type = std::char_traits; + using allocator_type = Allocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using tag_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using parse_state_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using string_type = std::basic_string; + using byte_string_type = std::vector; + + struct mapped_string + { + using allocator_type = Allocator; + + jsoncons::cbor::detail::cbor_major_type type; + string_type str; + byte_string_type bytes; + + mapped_string(const string_type& str, const allocator_type& alloc = allocator_type()) + : type(jsoncons::cbor::detail::cbor_major_type::text_string), str(str.c_str(), str.size(), alloc), bytes(alloc) + { + } + + mapped_string(string_type&& str, const allocator_type& alloc = allocator_type()) + : type(jsoncons::cbor::detail::cbor_major_type::text_string), str(std::move(str), alloc), bytes(alloc) + { + } + + mapped_string(const byte_string_type& bytes, + const allocator_type& alloc = allocator_type()) + : type(jsoncons::cbor::detail::cbor_major_type::byte_string), str(alloc), bytes(bytes,alloc) + { + } + + mapped_string(byte_string_type&& bytes, + const allocator_type& alloc = allocator_type()) + : type(jsoncons::cbor::detail::cbor_major_type::byte_string), str(alloc), bytes(std::move(bytes),alloc) + { + } + + mapped_string(const mapped_string&) = default; + + mapped_string(mapped_string&&) noexcept = default; + + mapped_string(const mapped_string& other, const allocator_type& alloc) + : type(other.type), str(other.str,alloc), bytes(other.bytes,alloc) + { + } + + mapped_string(mapped_string&& other, const allocator_type& alloc) noexcept + : type(other.type), str(std::move(other.str), alloc), bytes(std::move(other.bytes), alloc) + { + } + + mapped_string& operator=(const mapped_string&) = default; + + mapped_string& operator=(mapped_string&&) = default; + }; + + using mapped_string_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using stringref_map = std::vector; + using stringref_map_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + enum {stringref_tag, // 25 + stringref_namespace_tag, // 256 + item_tag, + num_of_tags}; + + std::bitset other_tags_; + + allocator_type alloc_; + Source source_; + cbor_decode_options options_; + + bool more_; + bool done_; + string_type text_buffer_; + byte_string_type bytes_buffer_; + uint64_t item_tag_; + std::vector state_stack_; + byte_string_type typed_array_; + std::vector shape_; + std::size_t index_; // TODO: Never used! + std::vector stringref_map_stack_; + int nesting_depth_; + + struct read_byte_string_from_buffer + { + byte_string_view bytes; + + read_byte_string_from_buffer(const byte_string_view& b) + : bytes(b) + { + } + template + void operator()(Container& c, std::error_code&) + { + c.clear(); + c.reserve(bytes.size()); + for (auto b : bytes) + { + c.push_back(b); + } + } + }; + + struct read_byte_string_from_source + { + basic_cbor_parser* source; + + read_byte_string_from_source(basic_cbor_parser* source) + : source(source) + { + } + template + void operator()(Container& cont, std::error_code& ec) + { + source->read_byte_string(cont,ec); + } + }; + +public: + template + basic_cbor_parser(Sourceable&& source, + const cbor_decode_options& options = cbor_decode_options(), + const Allocator& alloc = Allocator()) + : alloc_(alloc), + source_(std::forward(source)), + options_(options), + more_(true), + done_(false), + text_buffer_(alloc), + bytes_buffer_(alloc), + item_tag_(0), + state_stack_(alloc), + typed_array_(alloc), + index_(0), + stringref_map_stack_(alloc), + nesting_depth_(0) + { + state_stack_.emplace_back(parse_mode::root,0); + } + + void restart() + { + more_ = true; + } + + void reset() + { + more_ = true; + done_ = false; + text_buffer_.clear(); + bytes_buffer_.clear(); + item_tag_ = 0; + state_stack_.clear(); + state_stack_.emplace_back(parse_mode::root,0); + typed_array_.clear(); + stringref_map_stack_.clear(); + nesting_depth_ = 0; + } + + template + void reset(Sourceable&& source) + { + source_ = std::forward(source); + reset(); + } + + bool done() const + { + return done_; + } + + bool stopped() const + { + return !more_; + } + + std::size_t line() const override + { + return 0; + } + + std::size_t column() const override + { + return source_.position(); + } + + void parse(item_event_visitor& visitor, std::error_code& ec) + { + while (!done_ && more_) + { + switch (state_stack_.back().mode) + { + case parse_mode::multi_dim: + { + if (state_stack_.back().index == 0) + { + ++state_stack_.back().index; + read_item(visitor, ec); + } + else + { + produce_end_multi_dim(visitor, ec); + } + break; + } + case parse_mode::array: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + read_item(visitor, ec); + } + else + { + end_array(visitor, ec); + } + break; + } + case parse_mode::indefinite_array: + { + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == 0xff) + { + source_.ignore(1); + end_array(visitor, ec); + } + else + { + read_item(visitor, ec); + } + break; + } + case parse_mode::map_key: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + state_stack_.back().mode = parse_mode::map_value; + read_item(visitor, ec); + } + else + { + end_object(visitor, ec); + } + break; + } + case parse_mode::map_value: + { + state_stack_.back().mode = parse_mode::map_key; + read_item(visitor, ec); + break; + } + case parse_mode::indefinite_map_key: + { + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == 0xff) + { + source_.ignore(1); + end_object(visitor, ec); + } + else + { + state_stack_.back().mode = parse_mode::indefinite_map_value; + read_item(visitor, ec); + } + break; + } + case parse_mode::indefinite_map_value: + { + state_stack_.back().mode = parse_mode::indefinite_map_key; + read_item(visitor, ec); + break; + } + case parse_mode::root: + { + state_stack_.back().mode = parse_mode::accept; + read_item(visitor, ec); + break; + } + case parse_mode::accept: + { + JSONCONS_ASSERT(state_stack_.size() == 1); + state_stack_.clear(); + more_ = false; + done_ = true; + visitor.flush(); + break; + } + } + } + } +private: + void read_item(item_event_visitor& visitor, std::error_code& ec) + { + read_tags(ec); + if (!more_) + { + return; + } + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + jsoncons::cbor::detail::cbor_major_type major_type = get_major_type(c.value); + uint8_t info = get_additional_information_value(c.value); + + switch (major_type) + { + case jsoncons::cbor::detail::cbor_major_type::unsigned_integer: + { + uint64_t val = get_uint64_value(ec); + if (ec) + { + return; + } + if (!stringref_map_stack_.empty() && other_tags_[stringref_tag]) + { + other_tags_[stringref_tag] = false; + if (val >= stringref_map_stack_.back().size()) + { + ec = cbor_errc::stringref_too_large; + more_ = false; + return; + } + auto index = static_cast(val); + if (index != val) + { + ec = cbor_errc::number_too_large; + more_ = false; + return; + } + auto& str = stringref_map_stack_.back().at(index); + switch (str.type) + { + case jsoncons::cbor::detail::cbor_major_type::text_string: + { + handle_string(visitor, jsoncons::basic_string_view(str.str.data(),str.str.length()),ec); + if (ec) + { + return; + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::byte_string: + { + read_byte_string_from_buffer read(byte_string_view(str.bytes)); + write_byte_string(read, visitor, ec); + if (ec) + { + return; + } + break; + } + default: + JSONCONS_UNREACHABLE(); + break; + } + } + else + { + semantic_tag tag = semantic_tag::none; + if (other_tags_[item_tag]) + { + if (item_tag_ == 1) + { + tag = semantic_tag::epoch_second; + } + other_tags_[item_tag] = false; + } + more_ = visitor.uint64_value(val, tag, *this, ec); + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::negative_integer: + { + int64_t val = get_int64_value(ec); + if (ec) + { + return; + } + semantic_tag tag = semantic_tag::none; + if (other_tags_[item_tag]) + { + if (item_tag_ == 1) + { + tag = semantic_tag::epoch_second; + } + other_tags_[item_tag] = false; + } + more_ = visitor.int64_value(val, tag, *this, ec); + break; + } + case jsoncons::cbor::detail::cbor_major_type::byte_string: + { + read_byte_string_from_source read(this); + write_byte_string(read, visitor, ec); + if (ec) + { + return; + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::text_string: + { + text_buffer_.clear(); + + read_text_string(text_buffer_, ec); + if (ec) + { + return; + } + auto result = unicode_traits::validate(text_buffer_.data(),text_buffer_.size()); + if (result.ec != unicode_traits::conv_errc()) + { + ec = cbor_errc::invalid_utf8_text_string; + more_ = false; + return; + } + handle_string(visitor, jsoncons::basic_string_view(text_buffer_.data(),text_buffer_.length()),ec); + if (ec) + { + return; + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::semantic_tag: + { + JSONCONS_UNREACHABLE(); + break; + } + case jsoncons::cbor::detail::cbor_major_type::simple: + { + switch (info) + { + case 0x14: + more_ = visitor.bool_value(false, semantic_tag::none, *this, ec); + source_.ignore(1); + break; + case 0x15: + more_ = visitor.bool_value(true, semantic_tag::none, *this, ec); + source_.ignore(1); + break; + case 0x16: + more_ = visitor.null_value(semantic_tag::none, *this, ec); + source_.ignore(1); + break; + case 0x17: + more_ = visitor.null_value(semantic_tag::undefined, *this, ec); + source_.ignore(1); + break; + case 0x19: // Half-Precision Float (two-byte IEEE 754) + { + uint64_t val = get_uint64_value(ec); + if (ec) + { + return; + } + more_ = visitor.half_value(static_cast(val), semantic_tag::none, *this, ec); + break; + } + case 0x1a: // Single-Precision Float (four-byte IEEE 754) + case 0x1b: // Double-Precision Float (eight-byte IEEE 754) + { + double val = get_double(ec); + if (ec) + { + return; + } + semantic_tag tag = semantic_tag::none; + if (other_tags_[item_tag]) + { + if (item_tag_ == 1) + { + tag = semantic_tag::epoch_second; + } + other_tags_[item_tag] = false; + } + more_ = visitor.double_value(val, tag, *this, ec); + break; + } + default: + { + ec = cbor_errc::unknown_type; + more_ = false; + return; + } + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::array: + { + if (other_tags_[item_tag]) + { + switch (item_tag_) + { + case 0x04: + text_buffer_.clear(); + read_decimal_fraction(text_buffer_, ec); + if (ec) + { + return; + } + more_ = visitor.string_value(text_buffer_, semantic_tag::bigdec, *this, ec); + break; + case 0x05: + text_buffer_.clear(); + read_bigfloat(text_buffer_, ec); + if (ec) + { + return; + } + more_ = visitor.string_value(text_buffer_, semantic_tag::bigfloat, *this, ec); + break; + case 40: // row major storage + produce_begin_multi_dim(visitor, semantic_tag::multi_dim_row_major, ec); + break; + case 1040: // column major storage + produce_begin_multi_dim(visitor, semantic_tag::multi_dim_column_major, ec); + break; + default: + begin_array(visitor, info, ec); + break; + } + other_tags_[item_tag] = false; + } + else + { + begin_array(visitor, info, ec); + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::map: + { + begin_object(visitor, info, ec); + break; + } + default: + break; + } + other_tags_[item_tag] = false; + } + + void begin_array(item_event_visitor& visitor, uint8_t info, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = cbor_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + semantic_tag tag = semantic_tag::none; + bool pop_stringref_map_stack = false; + if (other_tags_[stringref_namespace_tag]) + { + stringref_map_stack_.emplace_back(); + other_tags_[stringref_namespace_tag] = false; + pop_stringref_map_stack = true; + } + switch (info) + { + case jsoncons::cbor::detail::additional_info::indefinite_length: + { + state_stack_.emplace_back(parse_mode::indefinite_array,0,pop_stringref_map_stack); + more_ = visitor.begin_array(tag, *this, ec); + source_.ignore(1); + break; + } + default: // definite length + { + std::size_t len = get_size(ec); + if (!more_) + { + return; + } + state_stack_.emplace_back(parse_mode::array,len,pop_stringref_map_stack); + more_ = visitor.begin_array(len, tag, *this, ec); + break; + } + } + } + + void end_array(item_event_visitor& visitor, std::error_code& ec) + { + --nesting_depth_; + + more_ = visitor.end_array(*this, ec); + if (state_stack_.back().pop_stringref_map_stack) + { + stringref_map_stack_.pop_back(); + } + state_stack_.pop_back(); + } + + void begin_object(item_event_visitor& visitor, uint8_t info, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = cbor_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + bool pop_stringref_map_stack = false; + if (other_tags_[stringref_namespace_tag]) + { + stringref_map_stack_.emplace_back(); + other_tags_[stringref_namespace_tag] = false; + pop_stringref_map_stack = true; + } + switch (info) + { + case jsoncons::cbor::detail::additional_info::indefinite_length: + { + state_stack_.emplace_back(parse_mode::indefinite_map_key,0,pop_stringref_map_stack); + more_ = visitor.begin_object(semantic_tag::none, *this, ec); + source_.ignore(1); + break; + } + default: // definite_length + { + std::size_t len = get_size(ec); + if (!more_) + { + return; + } + state_stack_.emplace_back(parse_mode::map_key,len,pop_stringref_map_stack); + more_ = visitor.begin_object(len, semantic_tag::none, *this, ec); + break; + } + } + } + + void end_object(item_event_visitor& visitor, std::error_code& ec) + { + --nesting_depth_; + more_ = visitor.end_object(*this, ec); + if (state_stack_.back().pop_stringref_map_stack) + { + stringref_map_stack_.pop_back(); + } + state_stack_.pop_back(); + } + + void read_text_string(string_type& str, std::error_code& ec) + { + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + jsoncons::cbor::detail::cbor_major_type major_type = get_major_type(c.value); + uint8_t info = get_additional_information_value(c.value); + + JSONCONS_ASSERT(major_type == jsoncons::cbor::detail::cbor_major_type::text_string); + auto func = [&str](Source& source, std::size_t length, std::error_code& ec) -> bool + { + if (source_reader::read(source, str, length) != length) + { + ec = cbor_errc::unexpected_eof; + return false; + } + return true; + }; + iterate_string_chunks(func, major_type, ec); + + if (!stringref_map_stack_.empty() && + info != jsoncons::cbor::detail::additional_info::indefinite_length && + str.length() >= jsoncons::cbor::detail::min_length_for_stringref(stringref_map_stack_.back().size())) + { + stringref_map_stack_.back().emplace_back(mapped_string(str,alloc_)); + } + + } + + std::size_t get_size(std::error_code& ec) + { + uint64_t u = get_uint64_value(ec); + if (!more_) + { + return 0; + } + std::size_t len = static_cast(u); + if (len != u) + { + ec = cbor_errc::number_too_large; + more_ = false; + } + return len; + } + + bool read_byte_string(byte_string_type& v, std::error_code& ec) + { + bool more = true; + v.clear(); + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more = false; + return more; + } + jsoncons::cbor::detail::cbor_major_type major_type = get_major_type(c.value); + uint8_t info = get_additional_information_value(c.value); + + JSONCONS_ASSERT(major_type == jsoncons::cbor::detail::cbor_major_type::byte_string); + + switch(info) + { + case jsoncons::cbor::detail::additional_info::indefinite_length: + { + auto func = [&v,&more](Source& source, std::size_t length, std::error_code& ec) -> bool + { + if (source_reader::read(source, v, length) != length) + { + ec = cbor_errc::unexpected_eof; + more = false; + return more; + } + return true; + }; + iterate_string_chunks(func, major_type, ec); + break; + } + default: + { + std::size_t length = get_size(ec); + if (ec) + { + more = false; + return more; + } + if (source_reader::read(source_, v, length) != length) + { + ec = cbor_errc::unexpected_eof; + more = false; + return more; + } + if (!stringref_map_stack_.empty() && + v.size() >= jsoncons::cbor::detail::min_length_for_stringref(stringref_map_stack_.back().size())) + { + stringref_map_stack_.back().emplace_back(mapped_string(v, alloc_)); + } + break; + } + + } + return more; + } + + template + void iterate_string_chunks(Function& func, jsoncons::cbor::detail::cbor_major_type type, std::error_code& ec) + { + int nesting_level = 0; + + bool done = false; + while (!done) + { + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + if (nesting_level > 0 && c.value == 0xff) + { + --nesting_level; + if (nesting_level == 0) + { + done = true; + } + source_.ignore(1); + continue; + } + + jsoncons::cbor::detail::cbor_major_type major_type = get_major_type(c.value); + if (major_type != type) + { + ec = cbor_errc::illegal_chunked_string; + more_ = false; + return; + } + uint8_t info = get_additional_information_value(c.value); + + switch (info) + { + case jsoncons::cbor::detail::additional_info::indefinite_length: + { + ++nesting_level; + source_.ignore(1); + break; + } + default: // definite length + { + std::size_t length = get_size(ec); + if (!more_) + { + return; + } + more_ = func(source_, length, ec); + if (!more_) + { + return; + } + if (nesting_level == 0) + { + done = true; + } + break; + } + } + } + } + + uint64_t get_uint64_value(std::error_code& ec) + { + uint64_t val = 0; + + uint8_t initial_b; + if (source_.read(&initial_b, 1) == 0) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return 0; + } + uint8_t info = get_additional_information_value(initial_b); + switch (info) + { + case JSONCONS_CBOR_0x00_0x17: // Integer 0x00..0x17 (0..23) + { + val = info; + break; + } + + case 0x18: // Unsigned integer (one-byte uint8_t follows) + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return val; + } + val = b; + break; + } + + case 0x19: // Unsigned integer (two-byte uint16_t follows) + { + uint8_t buf[sizeof(uint16_t)]; + source_.read(buf, sizeof(uint16_t)); + val = binary::big_to_native(buf, sizeof(buf)); + break; + } + + case 0x1a: // Unsigned integer (four-byte uint32_t follows) + { + uint8_t buf[sizeof(uint32_t)]; + source_.read(buf, sizeof(uint32_t)); + val = binary::big_to_native(buf, sizeof(buf)); + break; + } + + case 0x1b: // Unsigned integer (eight-byte uint64_t follows) + { + uint8_t buf[sizeof(uint64_t)]; + source_.read(buf, sizeof(uint64_t)); + val = binary::big_to_native(buf, sizeof(buf)); + break; + } + default: + break; + } + return val; + } + + int64_t get_int64_value(std::error_code& ec) + { + int64_t val = 0; + + auto ch = source_.peek(); + if (ch.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return val; + } + + jsoncons::cbor::detail::cbor_major_type major_type = get_major_type(ch.value); + uint8_t info = get_additional_information_value(ch.value); + switch (major_type) + { + case jsoncons::cbor::detail::cbor_major_type::negative_integer: + source_.ignore(1); + switch (info) + { + case JSONCONS_CBOR_0x00_0x17: // 0x00..0x17 (0..23) + { + val = static_cast(- 1 - info); + break; + } + case 0x18: // Negative integer (one-byte uint8_t follows) + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return val; + } + val = static_cast(-1) - static_cast(b); + break; + } + + case 0x19: // Negative integer -1-n (two-byte uint16_t follows) + { + uint8_t buf[sizeof(uint16_t)]; + if (source_.read(buf, sizeof(uint16_t)) != sizeof(uint16_t)) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return val; + } + auto x = binary::big_to_native(buf, sizeof(buf)); + val = static_cast(-1)- x; + break; + } + + case 0x1a: // Negative integer -1-n (four-byte uint32_t follows) + { + uint8_t buf[sizeof(uint32_t)]; + if (source_.read(buf, sizeof(uint32_t)) != sizeof(uint32_t)) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return val; + } + auto x = binary::big_to_native(buf, sizeof(buf)); + val = static_cast(-1)- x; + break; + } + + case 0x1b: // Negative integer -1-n (eight-byte uint64_t follows) + { + uint8_t buf[sizeof(uint64_t)]; + if (source_.read(buf, sizeof(uint64_t)) != sizeof(uint64_t)) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return val; + } + auto x = binary::big_to_native(buf, sizeof(buf)); + val = static_cast(-1)- static_cast(x); + break; + } + } + break; + + case jsoncons::cbor::detail::cbor_major_type::unsigned_integer: + { + uint64_t x = get_uint64_value(ec); + if (ec) + { + return 0; + } + if (x <= static_cast((std::numeric_limits::max)())) + { + val = x; + } + else + { + // error; + } + + break; + } + break; + default: + break; + } + + return val; + } + + double get_double(std::error_code& ec) + { + double val = 0; + + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return 0; + } + uint8_t info = get_additional_information_value(b); + switch (info) + { + case 0x1a: // Single-Precision Float (four-byte IEEE 754) + { + uint8_t buf[sizeof(float)]; + if (source_.read(buf, sizeof(float)) !=sizeof(float)) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return 0; + } + val = binary::big_to_native(buf, sizeof(buf)); + break; + } + + case 0x1b: // Double-Precision Float (eight-byte IEEE 754) + { + uint8_t buf[sizeof(double)]; + if (source_.read(buf, sizeof(double)) != sizeof(double)) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return 0; + } + val = binary::big_to_native(buf, sizeof(buf)); + break; + } + default: + break; + } + + return val; + } + + void read_decimal_fraction(string_type& result, std::error_code& ec) + { + std::size_t size = get_size(ec); + if (!more_) + { + return; + } + if (size != 2) + { + ec = cbor_errc::invalid_decimal_fraction; + more_ = false; + return; + } + + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + int64_t exponent = 0; + switch (get_major_type(c.value)) + { + case jsoncons::cbor::detail::cbor_major_type::unsigned_integer: + { + exponent = get_uint64_value(ec); + if (ec) + { + return; + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::negative_integer: + { + exponent = get_int64_value(ec); + if (ec) + { + return; + } + break; + } + default: + { + ec = cbor_errc::invalid_decimal_fraction; + more_ = false; + return; + } + } + + string_type str(alloc_); + + c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + + switch (get_major_type(c.value)) + { + case jsoncons::cbor::detail::cbor_major_type::unsigned_integer: + { + uint64_t val = get_uint64_value(ec); + if (ec) + { + return; + } + jsoncons::detail::from_integer(val, str); + break; + } + case jsoncons::cbor::detail::cbor_major_type::negative_integer: + { + int64_t val = get_int64_value(ec); + if (ec) + { + return; + } + jsoncons::detail::from_integer(val, str); + break; + } + case jsoncons::cbor::detail::cbor_major_type::semantic_tag: + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + uint8_t tag = get_additional_information_value(b); + c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + + if (get_major_type(c.value) == jsoncons::cbor::detail::cbor_major_type::byte_string) + { + bytes_buffer_.clear(); + read_byte_string(bytes_buffer_, ec); + if (ec) + { + more_ = false; + return; + } + if (tag == 2) + { + bigint n = bigint::from_bytes_be(1, bytes_buffer_.data(), bytes_buffer_.size()); + n.write_string(str); + } + else if (tag == 3) + { + bigint n = bigint::from_bytes_be(1, bytes_buffer_.data(), bytes_buffer_.size()); + n = -1 - n; + n.write_string(str); + } + } + break; + } + default: + { + ec = cbor_errc::invalid_decimal_fraction; + more_ = false; + return; + } + } + + if (str.size() >= static_cast((std::numeric_limits::max)()) || + exponent >= (std::numeric_limits::max)() || + exponent <= (std::numeric_limits::min)()) + { + ec = cbor_errc::invalid_decimal_fraction; + more_ = false; + return; + } + else if (str.size() > 0) + { + if (str[0] == '-') + { + result.push_back('-'); + jsoncons::detail::prettify_string(str.c_str()+1, str.size()-1, (int)exponent, -4, 17, result); + } + else + { + jsoncons::detail::prettify_string(str.c_str(), str.size(), (int)exponent, -4, 17, result); + } + } + else + { + ec = cbor_errc::invalid_decimal_fraction; + more_ = false; + return; + } + } + + void read_bigfloat(string_type& str, std::error_code& ec) + { + std::size_t size = get_size(ec); + if (!more_) + { + return; + } + if (size != 2) + { + ec = cbor_errc::invalid_bigfloat; + more_ = false; + return; + } + + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + int64_t exponent = 0; + switch (get_major_type(c.value)) + { + case jsoncons::cbor::detail::cbor_major_type::unsigned_integer: + { + exponent = get_uint64_value(ec); + if (ec) + { + return; + } + break; + } + case jsoncons::cbor::detail::cbor_major_type::negative_integer: + { + exponent = get_int64_value(ec); + if (ec) + { + return; + } + break; + } + default: + { + ec = cbor_errc::invalid_bigfloat; + more_ = false; + return; + } + } + + c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + switch (get_major_type(c.value)) + { + case jsoncons::cbor::detail::cbor_major_type::unsigned_integer: + { + uint64_t val = get_uint64_value(ec); + if (ec) + { + return; + } + str.push_back('0'); + str.push_back('x'); + jsoncons::detail::integer_to_string_hex(val, str); + break; + } + case jsoncons::cbor::detail::cbor_major_type::negative_integer: + { + int64_t val = get_int64_value(ec); + if (ec) + { + return; + } + str.push_back('-'); + str.push_back('0'); + str.push_back('x'); + jsoncons::detail::integer_to_string_hex(static_cast(-val), str); + break; + } + case jsoncons::cbor::detail::cbor_major_type::semantic_tag: + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + uint8_t tag = get_additional_information_value(b); + + c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + + if (get_major_type(c.value) == jsoncons::cbor::detail::cbor_major_type::byte_string) + { + bytes_buffer_.clear(); + more_ = read_byte_string(bytes_buffer_, ec); + if (!more_) + { + return; + } + if (tag == 2) + { + str.push_back('0'); + str.push_back('x'); + bigint n = bigint::from_bytes_be(1, bytes_buffer_.data(), bytes_buffer_.size()); + n.write_string_hex(str); + } + else if (tag == 3) + { + str.push_back('-'); + str.push_back('0'); + bigint n = bigint::from_bytes_be(1, bytes_buffer_.data(), bytes_buffer_.size()); + n = -1 - n; + n.write_string_hex(str); + str[2] = 'x'; // overwrite minus + } + } + break; + } + default: + { + ec = cbor_errc::invalid_bigfloat; + more_ = false; + return; + } + } + + str.push_back('p'); + if (exponent >=0) + { + jsoncons::detail::integer_to_string_hex(static_cast(exponent), str); + } + else + { + str.push_back('-'); + jsoncons::detail::integer_to_string_hex(static_cast(-exponent), str); + } + } + + static jsoncons::cbor::detail::cbor_major_type get_major_type(uint8_t type) + { + static constexpr uint8_t major_type_shift = 0x05; + uint8_t value = type >> major_type_shift; + return static_cast(value); + } + + static uint8_t get_additional_information_value(uint8_t type) + { + static constexpr uint8_t additional_information_mask = (1U << 5) - 1; + uint8_t value = type & additional_information_mask; + return value; + } + + void read_tags(std::error_code& ec) + { + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + jsoncons::cbor::detail::cbor_major_type major_type = get_major_type(c.value); + + while (major_type == jsoncons::cbor::detail::cbor_major_type::semantic_tag) + { + uint64_t val = get_uint64_value(ec); + if (!more_) + { + return; + } + switch(val) + { + case 25: // stringref + other_tags_[stringref_tag] = true; + break; + case 256: // stringref-namespace + other_tags_[stringref_namespace_tag] = true; + break; + default: + other_tags_[item_tag] = true; + item_tag_ = val; + break; + } + c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + major_type = get_major_type(c.value); + } + } + + void handle_string(item_event_visitor& visitor, const jsoncons::basic_string_view& v, std::error_code& ec) + { + semantic_tag tag = semantic_tag::none; + if (other_tags_[item_tag]) + { + switch (item_tag_) + { + case 0: + tag = semantic_tag::datetime; + break; + case 32: + tag = semantic_tag::uri; + break; + case 33: + tag = semantic_tag::base64url; + break; + case 34: + tag = semantic_tag::base64; + break; + default: + break; + } + other_tags_[item_tag] = false; + } + more_ = visitor.string_value(v, tag, *this, ec); + } + + static jsoncons::endian get_typed_array_endianness(const uint8_t tag) + { + return ((tag & detail::cbor_array_tags_e_mask) >> detail::cbor_array_tags_e_shift) == 0 ? jsoncons::endian::big : jsoncons::endian::little; + } + + static std::size_t get_typed_array_bytes_per_element(const uint8_t tag) + { + const uint8_t f = (tag & detail::cbor_array_tags_f_mask) >> detail::cbor_array_tags_f_shift; + const uint8_t ll = (tag & detail::cbor_array_tags_ll_mask) >> detail::cbor_array_tags_ll_shift; + + return std::size_t(1) << (f + ll); + } + + template + void write_byte_string(Read read, item_event_visitor& visitor, std::error_code& ec) + { + if (other_tags_[item_tag]) + { + switch (item_tag_) + { + case 0x2: + { + bytes_buffer_.clear(); + read(bytes_buffer_,ec); + if (ec) + { + more_ = false; + return; + } + bigint n = bigint::from_bytes_be(1, bytes_buffer_.data(), bytes_buffer_.size()); + text_buffer_.clear(); + n.write_string(text_buffer_); + more_ = visitor.string_value(text_buffer_, semantic_tag::bigint, *this, ec); + break; + } + case 0x3: + { + bytes_buffer_.clear(); + read(bytes_buffer_,ec); + if (ec) + { + more_ = false; + return; + } + bigint n = bigint::from_bytes_be(1, bytes_buffer_.data(), bytes_buffer_.size()); + n = -1 - n; + text_buffer_.clear(); + n.write_string(text_buffer_); + more_ = visitor.string_value(text_buffer_, semantic_tag::bigint, *this, ec); + break; + } + case 0x15: + { + read(bytes_buffer_,ec); + if (ec) + { + more_ = false; + return; + } + more_ = visitor.byte_string_value(bytes_buffer_, semantic_tag::base64url, *this, ec); + break; + } + case 0x16: + { + read(bytes_buffer_,ec); + if (ec) + { + more_ = false; + return; + } + more_ = visitor.byte_string_value(bytes_buffer_, semantic_tag::base64, *this, ec); + break; + } + case 0x17: + { + read(bytes_buffer_,ec); + if (ec) + { + more_ = false; + return; + } + more_ = visitor.byte_string_value(bytes_buffer_, semantic_tag::base16, *this, ec); + break; + } + case 0x40: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + uint8_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size(); + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x44: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + uint8_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size(); + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::clamped, *this, ec); + break; + } + case 0x41: + case 0x45: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + uint16_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x42: + case 0x46: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + uint32_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x43: + case 0x47: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + uint64_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x48: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + int8_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size(); + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x49: + case 0x4d: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + int16_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x4a: + case 0x4e: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + int32_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x4b: + case 0x4f: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + int64_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x50: + case 0x54: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + uint16_t* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(half_arg, jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x51: + case 0x55: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + float* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + case 0x52: + case 0x56: + { + typed_array_.clear(); + read(typed_array_,ec); + if (ec) + { + more_ = false; + return; + } + const uint8_t tag = (uint8_t)item_tag_; + jsoncons::endian e = get_typed_array_endianness(tag); + const size_t bytes_per_elem = get_typed_array_bytes_per_element(tag); + + double* data = reinterpret_cast(typed_array_.data()); + std::size_t size = typed_array_.size()/bytes_per_elem; + + if (e != jsoncons::endian::native) + { + for (std::size_t i = 0; i < size; ++i) + { + data[i] = binary::byte_swap(data[i]); + } + } + more_ = visitor.typed_array(jsoncons::span(data,size), semantic_tag::none, *this, ec); + break; + } + default: + { + read(bytes_buffer_,ec); + if (ec) + { + more_ = false; + return; + } + more_ = visitor.byte_string_value(bytes_buffer_, item_tag_, *this, ec); + break; + } + } + other_tags_[item_tag] = false; + } + else + { + read(bytes_buffer_,ec); + if (ec) + { + return; + } + more_ = visitor.byte_string_value(bytes_buffer_, semantic_tag::none, *this, ec); + } + } + + void produce_begin_multi_dim(item_event_visitor& visitor, + semantic_tag tag, + std::error_code& ec) + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + jsoncons::cbor::detail::cbor_major_type major_type = get_major_type(b); + JSONCONS_ASSERT(major_type == jsoncons::cbor::detail::cbor_major_type::array); + uint8_t info = get_additional_information_value(b); + + read_shape(info, ec); + if (ec) + { + return; + } + + state_stack_.emplace_back(parse_mode::multi_dim, 0); + more_ = visitor.begin_multi_dim(shape_, tag, *this, ec); + } + + void produce_end_multi_dim(item_event_visitor& visitor, std::error_code& ec) + { + more_ = visitor.end_multi_dim(*this, ec); + state_stack_.pop_back(); + } + + void read_shape(uint8_t info, std::error_code& ec) + { + shape_.clear(); + switch (info) + { + case jsoncons::cbor::detail::additional_info::indefinite_length: + { + while (true) + { + auto c = source_.peek(); + if (c.eof) + { + ec = cbor_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == 0xff) + { + source_.ignore(1); + } + else + { + std::size_t dim = get_size(ec); + if (!more_) + { + return; + } + shape_.push_back(dim); + } + } + break; + } + default: + { + std::size_t size = get_size(ec); + if (!more_) + { + return; + } + for (std::size_t i = 0; more_ && i < size; ++i) + { + std::size_t dim = get_size(ec); + if (!more_) + { + return; + } + shape_.push_back(dim); + } + break; + } + } + } +}; + +}} + +#endif diff --git a/third_party/jsoncons_ext/cbor/cbor_reader.hpp b/third_party/jsoncons_ext/cbor/cbor_reader.hpp new file mode 100644 index 0000000000..5d216900aa --- /dev/null +++ b/third_party/jsoncons_ext/cbor/cbor_reader.hpp @@ -0,0 +1,111 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_CBOR_READER_HPP +#define JSONCONS_CBOR_CBOR_READER_HPP + +#include +#include +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace cbor { + +template > +class basic_cbor_reader +{ + using char_type = char; + + basic_cbor_parser parser_; + basic_item_event_visitor_to_json_visitor adaptor_; + item_event_visitor& visitor_; +public: + template + basic_cbor_reader(Sourceable&& source, + json_visitor& visitor, + const Allocator& alloc) + : basic_cbor_reader(std::forward(source), + visitor, + cbor_decode_options(), + alloc) + { + } + + template + basic_cbor_reader(Sourceable&& source, + json_visitor& visitor, + const cbor_decode_options& options = cbor_decode_options(), + const Allocator& alloc=Allocator()) + : parser_(std::forward(source), options, alloc), + adaptor_(visitor, alloc), visitor_(adaptor_) + { + } + template + basic_cbor_reader(Sourceable&& source, + item_event_visitor& visitor, + const Allocator& alloc) + : basic_cbor_reader(std::forward(source), + visitor, + cbor_decode_options(), + alloc) + { + } + + template + basic_cbor_reader(Sourceable&& source, + item_event_visitor& visitor, + const cbor_decode_options& options = cbor_decode_options(), + const Allocator& alloc=Allocator()) + : parser_(std::forward(source), options, alloc), + visitor_(visitor) + { + } + + void read() + { + std::error_code ec; + read(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line(),column())); + } + } + + void read(std::error_code& ec) + { + parser_.reset(); + parser_.parse(visitor_, ec); + if (ec) + { + return; + } + } + + std::size_t line() const + { + return parser_.line(); + } + + std::size_t column() const + { + return parser_.column(); + } +}; + +using cbor_stream_reader = basic_cbor_reader; + +using cbor_bytes_reader = basic_cbor_reader; + +}} + +#endif diff --git a/third_party/jsoncons_ext/cbor/decode_cbor.hpp b/third_party/jsoncons_ext/cbor/decode_cbor.hpp new file mode 100644 index 0000000000..98bd6d9ac0 --- /dev/null +++ b/third_party/jsoncons_ext/cbor/decode_cbor.hpp @@ -0,0 +1,204 @@ +// Copyright 2017-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_DECODE_CBOR_HPP +#define JSONCONS_CBOR_DECODE_CBOR_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace cbor { + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_cbor(const Source& v, + const cbor_decode_options& options = cbor_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_cbor_reader reader(v, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_cbor(const Source& v, + const cbor_decode_options& options = cbor_decode_options()) + { + basic_cbor_cursor cursor(v, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_cbor(std::istream& is, + const cbor_decode_options& options = cbor_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + cbor_stream_reader reader(is, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_cbor(std::istream& is, + const cbor_decode_options& options = cbor_decode_options()) + { + basic_cbor_cursor cursor(is, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_cbor(InputIt first, InputIt last, + const cbor_decode_options& options = cbor_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_cbor_reader> reader(binary_iterator_source(first, last), adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_cbor(InputIt first, InputIt last, + const cbor_decode_options& options = cbor_decode_options()) + { + basic_cbor_cursor> cursor(binary_iterator_source(first, last), options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + // With leading allocator_set parameter + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_cbor(const allocator_set& alloc_set, + const Source& v, + const cbor_decode_options& options = cbor_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_cbor_reader reader(v, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_cbor(const allocator_set& alloc_set, + const Source& v, + const cbor_decode_options& options = cbor_decode_options()) + { + basic_cbor_cursor cursor(v, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_cbor(const allocator_set& alloc_set, + std::istream& is, + const cbor_decode_options& options = cbor_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_cbor_reader reader(is, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_cbor(const allocator_set& alloc_set, + std::istream& is, + const cbor_decode_options& options = cbor_decode_options()) + { + basic_cbor_cursor cursor(is, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + +} // namespace cbor +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/cbor/encode_cbor.hpp b/third_party/jsoncons_ext/cbor/encode_cbor.hpp new file mode 100644 index 0000000000..c748d90322 --- /dev/null +++ b/third_party/jsoncons_ext/cbor/encode_cbor.hpp @@ -0,0 +1,152 @@ +// Copyright 2017-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CBOR_ENCODE_CBOR_HPP +#define JSONCONS_CBOR_ENCODE_CBOR_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace cbor { + + // to bytes + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_cbor(const T& j, + ByteContainer& cont, + const cbor_encode_options& options = cbor_encode_options()) + { + using char_type = typename T::char_type; + basic_cbor_encoder> encoder(cont, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_cbor(const T& val, ByteContainer& cont, + const cbor_encode_options& options = cbor_encode_options()) + { + basic_cbor_encoder> encoder(cont, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // stream + + template + typename std::enable_if::value,void>::type + encode_cbor(const T& j, + std::ostream& os, + const cbor_encode_options& options = cbor_encode_options()) + { + using char_type = typename T::char_type; + cbor_stream_encoder encoder(os, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_cbor(const T& val, + std::ostream& os, + const cbor_encode_options& options = cbor_encode_options()) + { + cbor_stream_encoder encoder(os, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // temp_allocator_arg + + // to bytes + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_cbor(const allocator_set& alloc_set, + const T& j, + ByteContainer& cont, + const cbor_encode_options& options = cbor_encode_options()) + { + using char_type = typename T::char_type; + basic_cbor_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_cbor(const allocator_set& alloc_set, + const T& val, + ByteContainer& cont, + const cbor_encode_options& options = cbor_encode_options()) + { + basic_cbor_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // stream + + template + typename std::enable_if::value,void>::type + encode_cbor(const allocator_set& alloc_set, + const T& j, + std::ostream& os, + const cbor_encode_options& options = cbor_encode_options()) + { + using char_type = typename T::char_type; + basic_cbor_encoder encoder(os, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_cbor(const allocator_set& alloc_set, + const T& val, + std::ostream& os, + const cbor_encode_options& options = cbor_encode_options()) + { + basic_cbor_encoder encoder(os, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + +} // namespace cbor +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/csv/csv.hpp b/third_party/jsoncons_ext/csv/csv.hpp new file mode 100644 index 0000000000..3421cffddd --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv.hpp @@ -0,0 +1,17 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_HPP +#define JSONCONS_CSV_CSV_HPP + +#include +#include +#include +#include +#include +#include + +#endif diff --git a/third_party/jsoncons_ext/csv/csv_cursor.hpp b/third_party/jsoncons_ext/csv/csv_cursor.hpp new file mode 100644 index 0000000000..19ce225e00 --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv_cursor.hpp @@ -0,0 +1,340 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_CURSOR_HPP +#define JSONCONS_CSV_CSV_CURSOR_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace csv { + +template ,typename Allocator=std::allocator> +class basic_csv_cursor : public basic_staj_cursor, private virtual ser_context +{ +public: + using source_type = Source; + using char_type = CharT; + using allocator_type = Allocator; +private: + static constexpr size_t default_max_buffer_size = 16384; + + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + text_source_adaptor source_; + basic_csv_parser parser_; + basic_staj_visitor cursor_visitor_; + + // Noncopyable and nonmoveable + basic_csv_cursor(const basic_csv_cursor&) = delete; + basic_csv_cursor& operator=(const basic_csv_cursor&) = delete; + +public: + using string_view_type = jsoncons::basic_string_view; + + // Constructors that throw parse exceptions + + template + basic_csv_cursor(Sourceable&& source, + const basic_csv_decode_options& options = basic_csv_decode_options(), + std::function err_handler = default_csv_parsing(), + const Allocator& alloc = Allocator(), + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(std::forward(source)), + parser_(options,err_handler,alloc), + cursor_visitor_(accept_all) + { + if (!done()) + { + next(); + } + } + + template + basic_csv_cursor(Sourceable&& source, + const basic_csv_decode_options& options = basic_csv_decode_options(), + std::function err_handler = default_csv_parsing(), + const Allocator& alloc = Allocator(), + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(), + parser_(options,err_handler,alloc), + cursor_visitor_(accept_all) + { + jsoncons::basic_string_view sv(std::forward(source)); + initialize_with_string_view(sv); + } + + + // Constructors that set parse error codes + template + basic_csv_cursor(Sourceable&& source, + std::error_code& ec) + : basic_csv_cursor(std::allocator_arg, Allocator(), + std::forward(source), + basic_csv_decode_options(), + default_csv_parsing(), + ec) + { + } + + template + basic_csv_cursor(Sourceable&& source, + const basic_csv_decode_options& options, + std::error_code& ec) + : basic_csv_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + default_csv_parsing(), + ec) + { + } + + template + basic_csv_cursor(Sourceable&& source, + const basic_csv_decode_options& options, + std::function err_handler, + std::error_code& ec) + : basic_csv_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + err_handler, + ec) + { + } + + template + basic_csv_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const basic_csv_decode_options& options, + std::function err_handler, + std::error_code& ec, + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(std::forward(source)), + parser_(options,err_handler,alloc), + cursor_visitor_(accept_all) + { + if (!done()) + { + next(ec); + } + } + + template + basic_csv_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const basic_csv_decode_options& options, + std::function err_handler, + std::error_code& ec, + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(), + parser_(options,err_handler,alloc), + cursor_visitor_(accept_all) + { + jsoncons::basic_string_view sv(std::forward(source)); + initialize_with_string_view(sv, ec); + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source) + { + source_ = std::forward(source); + parser_.reinitialize(); + cursor_visitor_.reset(); + if (!done()) + { + next(); + } + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source) + { + source_ = {}; + parser_.reinitialize(); + cursor_visitor_.reset(); + initialize_with_string_view(std::forward(source)); + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source, std::error_code& ec) + { + source_ = std::forward(source); + parser_.reinitialize(); + cursor_visitor_.reset(); + if (!done()) + { + next(ec); + } + } + + template + typename std::enable_if,Sourceable>::value>::type + reset(Sourceable&& source, std::error_code& ec) + { + source_ = {}; + parser_.reinitialize(); + initialize_with_string_view(std::forward(source), ec); + } + + bool done() const override + { + return parser_.done(); + } + + const basic_staj_event& current() const override + { + return cursor_visitor_.event(); + } + + void read_to(basic_json_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_json_visitor& visitor, + std::error_code& ec) override + { + if (cursor_visitor_.event().send_json_event(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + static bool accept_all(const basic_staj_event&, const ser_context&) + { + return true; + } + + const ser_context& context() const override + { + return *this; + } + + bool eof() const + { + return parser_.source_exhausted() && source_.eof(); + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + basic_staj_filter_view operator|(basic_csv_cursor& cursor, + std::function&, const ser_context&)> pred) + { + return basic_staj_filter_view(cursor, pred); + } + +private: + + void initialize_with_string_view(string_view_type sv) + { + auto r = unicode_traits::detect_json_encoding(sv.data(), sv.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + JSONCONS_THROW(ser_error(json_errc::illegal_unicode_character,parser_.line(),parser_.column())); + } + std::size_t offset = (r.ptr - sv.data()); + parser_.update(sv.data()+offset,sv.size()-offset); + if (!done()) + { + next(); + } + } + + void initialize_with_string_view(string_view_type sv, std::error_code& ec) + { + auto r = unicode_traits::detect_encoding_from_bom(sv.data(), sv.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + ec = json_errc::illegal_unicode_character; + return; + } + std::size_t offset = (r.ptr - sv.data()); + parser_.update(sv.data()+offset,sv.size()-offset); + if (!done()) + { + next(ec); + } + } + + void read_next(std::error_code& ec) + { + read_next(cursor_visitor_, ec); + } + + void read_next(basic_json_visitor& visitor, std::error_code& ec) + { + parser_.restart(); + while (!parser_.stopped()) + { + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + } + } + parser_.parse_some(visitor, ec); + if (ec) return; + } + } +}; + +using csv_stream_cursor = basic_csv_cursor>; +using csv_string_cursor = basic_csv_cursor>; +using wcsv_stream_cursor = basic_csv_cursor>; +using wcsv_string_cursor = basic_csv_cursor>; + +}} + +#endif + diff --git a/third_party/jsoncons_ext/csv/csv_encoder.hpp b/third_party/jsoncons_ext/csv/csv_encoder.hpp new file mode 100644 index 0000000000..2aec0bbeb3 --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv_encoder.hpp @@ -0,0 +1,942 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_ENCODER_HPP +#define JSONCONS_CSV_CSV_ENCODER_HPP + +#include // std::array +#include +#include +#include +#include // std::move +#include // std::unordered_map +#include // std::allocator +#include // std::numeric_limits +#include +#include +#include +#include +#include + +namespace jsoncons { namespace csv { + +template ,typename Allocator=std::allocator> +class basic_csv_encoder final : public basic_json_visitor +{ +public: + using char_type = CharT; + using typename basic_json_visitor::string_view_type; + using sink_type = Sink; + + using allocator_type = Allocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using string_type = std::basic_string, char_allocator_type>; + using string_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using string_string_allocator_type = typename std::allocator_traits:: template rebind_alloc>; + +private: + static jsoncons::basic_string_view null_constant() + { + static jsoncons::basic_string_view k = JSONCONS_STRING_VIEW_CONSTANT(CharT,"null"); + return k; + } + static jsoncons::basic_string_view true_constant() + { + static jsoncons::basic_string_view k = JSONCONS_STRING_VIEW_CONSTANT(CharT,"true"); + return k; + } + static jsoncons::basic_string_view false_constant() + { + static jsoncons::basic_string_view k = JSONCONS_STRING_VIEW_CONSTANT(CharT,"false"); + return k; + } + + enum class stack_item_kind + { + row_mapping, + column_mapping, + object, + row, + column, + object_multi_valued_field, + row_multi_valued_field, + column_multi_valued_field + }; + + struct stack_item + { + stack_item_kind item_kind_; + std::size_t count_; + + stack_item(stack_item_kind item_kind) noexcept + : item_kind_(item_kind), count_(0) + { + } + + bool is_object() const + { + return item_kind_ == stack_item_kind::object; + } + + stack_item_kind item_kind() const + { + return item_kind_; + } + }; + + Sink sink_; + const basic_csv_encode_options options_; + allocator_type alloc_; + + std::vector stack_; + jsoncons::detail::write_double fp_; + std::vector strings_buffer_; + + std::unordered_map,std::equal_to,string_string_allocator_type> buffered_line_; + string_type name_; + std::size_t column_index_; + std::vector row_counts_; + + // Noncopyable and nonmoveable + basic_csv_encoder(const basic_csv_encoder&) = delete; + basic_csv_encoder& operator=(const basic_csv_encoder&) = delete; +public: + basic_csv_encoder(Sink&& sink, + const Allocator& alloc = Allocator()) + : basic_csv_encoder(std::forward(sink), basic_csv_encode_options(), alloc) + { + } + + basic_csv_encoder(Sink&& sink, + const basic_csv_encode_options& options, + const Allocator& alloc = Allocator()) + : sink_(std::forward(sink)), + options_(options), + alloc_(alloc), + stack_(), + fp_(options.float_format(), options.precision()), + column_index_(0) + { + jsoncons::csv::detail::parse_column_names(options.column_names(), strings_buffer_); + } + + ~basic_csv_encoder() noexcept + { + JSONCONS_TRY + { + sink_.flush(); + } + JSONCONS_CATCH(...) + { + } + } + + void reset() + { + stack_.clear(); + strings_buffer_.clear(); + buffered_line_.clear(); + name_.clear(); + column_index_ = 0; + row_counts_.clear(); + } + + void reset(Sink&& sink) + { + sink_ = std::move(sink); + reset(); + } + +private: + + template + void escape_string(const CharT* s, + std::size_t length, + CharT quote_char, CharT quote_escape_char, + AnyWriter& sink) + { + const CharT* begin = s; + const CharT* end = s + length; + for (const CharT* it = begin; it != end; ++it) + { + CharT c = *it; + if (c == quote_char) + { + sink.push_back(quote_escape_char); + sink.push_back(quote_char); + } + else + { + sink.push_back(c); + } + } + } + + void visit_flush() override + { + sink_.flush(); + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (stack_.empty()) + { + stack_.emplace_back(stack_item_kind::column_mapping); + return true; + } + switch (stack_.back().item_kind_) + { + case stack_item_kind::row_mapping: + stack_.emplace_back(stack_item_kind::object); + return true; + default: // error + ec = csv_errc::source_error; + return false; + } + } + + bool visit_end_object(const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + if (stack_[0].count_ == 0) + { + for (std::size_t i = 0; i < strings_buffer_.size(); ++i) + { + if (i > 0) + { + sink_.push_back(options_.field_delimiter()); + } + sink_.append(strings_buffer_[i].data(), + strings_buffer_[i].length()); + } + sink_.append(options_.line_delimiter().data(), + options_.line_delimiter().length()); + } + for (std::size_t i = 0; i < strings_buffer_.size(); ++i) + { + if (i > 0) + { + sink_.push_back(options_.field_delimiter()); + } + auto it = buffered_line_.find(strings_buffer_[i]); + if (it != buffered_line_.end()) + { + sink_.append(it->second.data(),it->second.length()); + it->second.clear(); + } + } + sink_.append(options_.line_delimiter().data(), options_.line_delimiter().length()); + break; + case stack_item_kind::column_mapping: + { + for (const auto& item : strings_buffer_) + { + sink_.append(item.data(), item.size()); + sink_.append(options_.line_delimiter().data(), options_.line_delimiter().length()); + } + break; + } + default: + break; + } + stack_.pop_back(); + if (!stack_.empty()) + { + end_value(); + } + return true; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (stack_.empty()) + { + stack_.emplace_back(stack_item_kind::row_mapping); + return true; + } + switch (stack_.back().item_kind_) + { + case stack_item_kind::row_mapping: + stack_.emplace_back(stack_item_kind::row); + if (stack_[0].count_ == 0) + { + for (std::size_t i = 0; i < strings_buffer_.size(); ++i) + { + if (i > 0) + { + sink_.push_back(options_.field_delimiter()); + } + sink_.append(strings_buffer_[i].data(),strings_buffer_[i].length()); + } + if (strings_buffer_.size() > 0) + { + sink_.append(options_.line_delimiter().data(), + options_.line_delimiter().length()); + } + } + return true; + case stack_item_kind::object: + stack_.emplace_back(stack_item_kind::object_multi_valued_field); + return true; + case stack_item_kind::column_mapping: + stack_.emplace_back(stack_item_kind::column); + row_counts_.push_back(1); + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + return true; + case stack_item_kind::column: + { + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + begin_value(bo); + stack_.emplace_back(stack_item_kind::column_multi_valued_field); + return true; + } + case stack_item_kind::row: + begin_value(sink_); + stack_.emplace_back(stack_item_kind::row_multi_valued_field); + return true; + default: // error + ec = csv_errc::source_error; + return false; + } + } + + bool visit_end_array(const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::row: + sink_.append(options_.line_delimiter().data(), + options_.line_delimiter().length()); + break; + case stack_item_kind::column: + ++column_index_; + break; + default: + break; + } + stack_.pop_back(); + + if (!stack_.empty()) + { + end_value(); + } + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + { + name_ = string_type(name); + buffered_line_[string_type(name)] = std::basic_string(); + if (stack_[0].count_ == 0 && options_.column_names().size() == 0) + { + strings_buffer_.emplace_back(name); + } + break; + } + case stack_item_kind::column_mapping: + { + if (strings_buffer_.empty()) + { + strings_buffer_.emplace_back(name); + } + else + { + strings_buffer_[0].push_back(options_.field_delimiter()); + strings_buffer_[0].append(string_type(name)); + } + break; + } + default: + break; + } + return true; + } + + bool visit_null(semantic_tag, const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + case stack_item_kind::object_multi_valued_field: + { + auto it = buffered_line_.find(name_); + if (it != buffered_line_.end()) + { + std::basic_string s; + jsoncons::string_sink> bo(s); + write_null_value(bo); + bo.flush(); + if (!it->second.empty() && options_.subfield_delimiter() != char_type()) + { + it->second.push_back(options_.subfield_delimiter()); + } + it->second.append(s); + } + break; + } + case stack_item_kind::row: + case stack_item_kind::row_multi_valued_field: + write_null_value(sink_); + break; + case stack_item_kind::column: + { + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_null_value(bo); + break; + } + case stack_item_kind::column_multi_valued_field: + { + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_null_value(bo); + break; + } + default: + break; + } + return true; + } + + bool visit_string(const string_view_type& sv, semantic_tag, const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + case stack_item_kind::object_multi_valued_field: + { + auto it = buffered_line_.find(name_); + if (it != buffered_line_.end()) + { + std::basic_string s; + jsoncons::string_sink> bo(s); + write_string_value(sv,bo); + bo.flush(); + if (!it->second.empty() && options_.subfield_delimiter() != char_type()) + { + it->second.push_back(options_.subfield_delimiter()); + } + it->second.append(s); + } + break; + } + case stack_item_kind::row: + case stack_item_kind::row_multi_valued_field: + write_string_value(sv,sink_); + break; + case stack_item_kind::column: + { + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_string_value(sv,bo); + break; + } + case stack_item_kind::column_multi_valued_field: + { + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_string_value(sv,bo); + break; + } + default: + break; + } + return true; + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag tag, + const ser_context& context, + std::error_code& ec) override + { + byte_string_chars_format encoding_hint; + switch (tag) + { + case semantic_tag::base16: + encoding_hint = byte_string_chars_format::base16; + break; + case semantic_tag::base64: + encoding_hint = byte_string_chars_format::base64; + break; + case semantic_tag::base64url: + encoding_hint = byte_string_chars_format::base64url; + break; + default: + encoding_hint = byte_string_chars_format::none; + break; + } + byte_string_chars_format format = jsoncons::detail::resolve_byte_string_chars_format(encoding_hint,byte_string_chars_format::none,byte_string_chars_format::base64url); + + std::basic_string s; + switch (format) + { + case byte_string_chars_format::base16: + { + encode_base16(b.begin(),b.end(),s); + visit_string(s, semantic_tag::none, context, ec); + break; + } + case byte_string_chars_format::base64: + { + encode_base64(b.begin(),b.end(),s); + visit_string(s, semantic_tag::none, context, ec); + break; + } + case byte_string_chars_format::base64url: + { + encode_base64url(b.begin(),b.end(),s); + visit_string(s, semantic_tag::none, context, ec); + break; + } + default: + { + JSONCONS_UNREACHABLE(); + } + } + + return true; + } + + bool visit_double(double val, + semantic_tag, + const ser_context& context, + std::error_code& ec) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + case stack_item_kind::object_multi_valued_field: + { + auto it = buffered_line_.find(name_); + if (it != buffered_line_.end()) + { + std::basic_string s; + jsoncons::string_sink> bo(s); + write_double_value(val, context, bo, ec); + bo.flush(); + if (!it->second.empty() && options_.subfield_delimiter() != char_type()) + { + it->second.push_back(options_.subfield_delimiter()); + } + it->second.append(s); + } + break; + } + case stack_item_kind::row: + case stack_item_kind::row_multi_valued_field: + write_double_value(val, context, sink_, ec); + break; + case stack_item_kind::column: + { + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_double_value(val, context, bo, ec); + break; + } + case stack_item_kind::column_multi_valued_field: + { + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_double_value(val, context, bo, ec); + break; + } + default: + break; + } + return true; + } + + bool visit_int64(int64_t val, + semantic_tag, + const ser_context&, + std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + case stack_item_kind::object_multi_valued_field: + { + auto it = buffered_line_.find(name_); + if (it != buffered_line_.end()) + { + std::basic_string s; + jsoncons::string_sink> bo(s); + write_int64_value(val,bo); + bo.flush(); + if (!it->second.empty() && options_.subfield_delimiter() != char_type()) + { + it->second.push_back(options_.subfield_delimiter()); + } + it->second.append(s); + } + break; + } + case stack_item_kind::row: + case stack_item_kind::row_multi_valued_field: + write_int64_value(val,sink_); + break; + case stack_item_kind::column: + { + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_int64_value(val, bo); + break; + } + case stack_item_kind::column_multi_valued_field: + { + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_int64_value(val, bo); + break; + } + default: + break; + } + return true; + } + + bool visit_uint64(uint64_t val, + semantic_tag, + const ser_context&, + std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + case stack_item_kind::object_multi_valued_field: + { + auto it = buffered_line_.find(name_); + if (it != buffered_line_.end()) + { + std::basic_string s; + jsoncons::string_sink> bo(s); + write_uint64_value(val, bo); + bo.flush(); + if (!it->second.empty() && options_.subfield_delimiter() != char_type()) + { + it->second.push_back(options_.subfield_delimiter()); + } + it->second.append(s); + } + break; + } + case stack_item_kind::row: + case stack_item_kind::row_multi_valued_field: + write_uint64_value(val,sink_); + break; + case stack_item_kind::column: + { + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_uint64_value(val, bo); + break; + } + case stack_item_kind::column_multi_valued_field: + { + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_uint64_value(val, bo); + break; + } + default: + break; + } + return true; + } + + bool visit_bool(bool val, semantic_tag, const ser_context&, std::error_code&) override + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::object: + case stack_item_kind::object_multi_valued_field: + { + auto it = buffered_line_.find(name_); + if (it != buffered_line_.end()) + { + std::basic_string s; + jsoncons::string_sink> bo(s); + write_bool_value(val,bo); + bo.flush(); + if (!it->second.empty() && options_.subfield_delimiter() != char_type()) + { + it->second.push_back(options_.subfield_delimiter()); + } + it->second.append(s); + } + break; + } + case stack_item_kind::row: + case stack_item_kind::row_multi_valued_field: + write_bool_value(val,sink_); + break; + case stack_item_kind::column: + { + if (strings_buffer_.size() <= row_counts_.back()) + { + strings_buffer_.emplace_back(); + } + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_bool_value(val, bo); + break; + } + case stack_item_kind::column_multi_valued_field: + { + jsoncons::string_sink> bo(strings_buffer_[row_counts_.back()]); + write_bool_value(val, bo); + break; + } + default: + break; + } + return true; + } + + template + bool do_string_value(const CharT* s, std::size_t length, AnyWriter& sink) + { + bool quote = false; + if (options_.quote_style() == quote_style_kind::all || options_.quote_style() == quote_style_kind::nonnumeric || + (options_.quote_style() == quote_style_kind::minimal && + (std::char_traits::find(s, length, options_.field_delimiter()) != nullptr || std::char_traits::find(s, length, options_.quote_char()) != nullptr))) + { + quote = true; + sink.push_back(options_.quote_char()); + } + escape_string(s, length, options_.quote_char(), options_.quote_escape_char(), sink); + if (quote) + { + sink.push_back(options_.quote_char()); + } + + return true; + } + + template + void write_string_value(const string_view_type& value, AnyWriter& sink) + { + begin_value(sink); + do_string_value(value.data(),value.length(),sink); + end_value(); + } + + template + void write_double_value(double val, const ser_context& context, AnyWriter& sink, std::error_code& ec) + { + begin_value(sink); + + if (!std::isfinite(val)) + { + if ((std::isnan)(val)) + { + if (options_.enable_nan_to_num()) + { + sink.append(options_.nan_to_num().data(), options_.nan_to_num().length()); + } + else if (options_.enable_nan_to_str()) + { + visit_string(options_.nan_to_str(), semantic_tag::none, context, ec); + } + else + { + sink.append(null_constant().data(), null_constant().size()); + } + } + else if (val == std::numeric_limits::infinity()) + { + if (options_.enable_inf_to_num()) + { + sink.append(options_.inf_to_num().data(), options_.inf_to_num().length()); + } + else if (options_.enable_inf_to_str()) + { + visit_string(options_.inf_to_str(), semantic_tag::none, context, ec); + } + else + { + sink.append(null_constant().data(), null_constant().size()); + } + } + else + { + if (options_.enable_neginf_to_num()) + { + sink.append(options_.neginf_to_num().data(), options_.neginf_to_num().length()); + } + else if (options_.enable_neginf_to_str()) + { + visit_string(options_.neginf_to_str(), semantic_tag::none, context, ec); + } + else + { + sink.append(null_constant().data(), null_constant().size()); + } + } + } + else + { + fp_(val, sink); + } + + end_value(); + + } + + template + void write_int64_value(int64_t val, AnyWriter& sink) + { + begin_value(sink); + + jsoncons::detail::from_integer(val,sink); + + end_value(); + } + + template + void write_uint64_value(uint64_t val, AnyWriter& sink) + { + begin_value(sink); + + jsoncons::detail::from_integer(val,sink); + + end_value(); + } + + template + void write_bool_value(bool val, AnyWriter& sink) + { + begin_value(sink); + + if (val) + { + sink.append(true_constant().data(), true_constant().size()); + } + else + { + sink.append(false_constant().data(), false_constant().size()); + } + + end_value(); + } + + template + bool write_null_value(AnyWriter& sink) + { + begin_value(sink); + sink.append(null_constant().data(), null_constant().size()); + end_value(); + return true; + } + + template + void begin_value(AnyWriter& sink) + { + JSONCONS_ASSERT(!stack_.empty()); + switch (stack_.back().item_kind_) + { + case stack_item_kind::row: + if (stack_.back().count_ > 0) + { + sink.push_back(options_.field_delimiter()); + } + break; + case stack_item_kind::column: + { + if (row_counts_.size() >= 3) + { + for (std::size_t i = row_counts_.size()-2; i-- > 0;) + { + if (row_counts_[i] <= row_counts_.back()) + { + sink.push_back(options_.field_delimiter()); + } + else + { + break; + } + } + } + if (column_index_ > 0) + { + sink.push_back(options_.field_delimiter()); + } + break; + } + case stack_item_kind::row_multi_valued_field: + case stack_item_kind::column_multi_valued_field: + if (stack_.back().count_ > 0 && options_.subfield_delimiter() != char_type()) + { + sink.push_back(options_.subfield_delimiter()); + } + break; + default: + break; + } + } + + void end_value() + { + JSONCONS_ASSERT(!stack_.empty()); + switch(stack_.back().item_kind_) + { + case stack_item_kind::row: + { + ++stack_.back().count_; + break; + } + case stack_item_kind::column: + { + ++row_counts_.back(); + break; + } + default: + ++stack_.back().count_; + break; + } + } +}; + +using csv_stream_encoder = basic_csv_encoder; +using csv_string_encoder = basic_csv_encoder>; +using csv_wstream_encoder = basic_csv_encoder; +using wcsv_string_encoder = basic_csv_encoder>; + +}} + +#endif diff --git a/third_party/jsoncons_ext/csv/csv_error.hpp b/third_party/jsoncons_ext/csv/csv_error.hpp new file mode 100644 index 0000000000..b0d2491892 --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv_error.hpp @@ -0,0 +1,81 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_ERROR_HPP +#define JSONCONS_CSV_CSV_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace csv { + + enum class csv_errc : int + { + success = 0, + unexpected_eof = 1, + source_error, + expected_quote, + syntax_error, + invalid_parse_state, + invalid_escaped_char, + unexpected_char_between_fields + }; + +class csv_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/csv"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case csv_errc::unexpected_eof: + return "Unexpected end of file"; + case csv_errc::source_error: + return "Source error"; + case csv_errc::expected_quote: + return "Expected quote character"; + case csv_errc::syntax_error: + return "CSV syntax error"; + case csv_errc::invalid_parse_state: + return "Invalid CSV parser state"; + case csv_errc::invalid_escaped_char: + return "Invalid character following quote escape character"; + case csv_errc::unexpected_char_between_fields: + return "Unexpected character between fields"; + default: + return "Unknown CSV parser error"; + } + } +}; + +inline +const std::error_category& csv_error_category() +{ + static csv_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(csv_errc result) +{ + return std::error_code(static_cast(result),csv_error_category()); +} + +}} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons_ext/csv/csv_options.hpp b/third_party/jsoncons_ext/csv/csv_options.hpp new file mode 100644 index 0000000000..6b2cab5e1c --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv_options.hpp @@ -0,0 +1,944 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_OPTIONS_HPP +#define JSONCONS_CSV_CSV_OPTIONS_HPP + +#include +#include +#include // std::pair +#include // std::unordered_map +#include +#include // std::numeric_limits +#include +#include + +namespace jsoncons { namespace csv { + +enum class csv_column_type : uint8_t +{ + string_t,integer_t,float_t,boolean_t,repeat_t +}; + +enum class quote_style_kind : uint8_t +{ + minimal,all,nonnumeric,none +}; + +enum class csv_mapping_kind : uint8_t +{ + n_rows = 1, + n_objects, + m_columns +}; + +enum class column_state {sequence,label}; + +struct csv_type_info +{ + csv_type_info() = default; + csv_type_info(const csv_type_info&) = default; + csv_type_info(csv_type_info&&) = default; + + csv_type_info(csv_column_type ctype, std::size_t lev, std::size_t repcount = 0) noexcept + { + col_type = ctype; + level = lev; + rep_count = repcount; + } + + csv_column_type col_type; + std::size_t level; + std::size_t rep_count; +}; + +namespace detail { + +template +void parse_column_names(const std::basic_string& names, + Container& cont) +{ + column_state state = column_state::sequence; + typename Container::value_type buffer(cont.get_allocator()); + + auto p = names.begin(); + while (p != names.end()) + { + switch (state) + { + case column_state::sequence: + { + switch (*p) + { + case ' ': case '\t':case '\r': case '\n': + ++p; + break; + default: + buffer.clear(); + state = column_state::label; + break; + } + break; + } + case column_state::label: + { + switch (*p) + { + case ',': + cont.push_back(buffer); + buffer.clear(); + ++p; + state = column_state::sequence; + break; + default: + buffer.push_back(*p); + ++p; + break; + } + break; + } + } + } + if (state == column_state::label) + { + cont.push_back(buffer); + buffer.clear(); + } +} + +template +void parse_column_types(const std::basic_string& types, + Container& column_types) +{ + const std::map,csv_column_type> type_dictionary = + { + + {JSONCONS_STRING_VIEW_CONSTANT(CharT,"string"),csv_column_type::string_t}, + {JSONCONS_STRING_VIEW_CONSTANT(CharT,"integer"),csv_column_type::integer_t}, + {JSONCONS_STRING_VIEW_CONSTANT(CharT,"float"),csv_column_type::float_t}, + {JSONCONS_STRING_VIEW_CONSTANT(CharT,"boolean"),csv_column_type::boolean_t} + }; + + column_state state = column_state::sequence; + int depth = 0; + std::basic_string buffer; + + auto p = types.begin(); + while (p != types.end()) + { + switch (state) + { + case column_state::sequence: + { + switch (*p) + { + case ' ': case '\t':case '\r': case '\n': + ++p; + break; + case '[': + ++depth; + ++p; + break; + case ']': + JSONCONS_ASSERT(depth > 0); + --depth; + ++p; + break; + case '*': + { + JSONCONS_ASSERT(column_types.size() != 0); + std::size_t offset = 0; + std::size_t level = column_types.size() > 0 ? column_types.back().level: 0; + if (level > 0) + { + for (auto it = column_types.rbegin(); + it != column_types.rend() && level == it->level; + ++it) + { + ++offset; + } + } + else + { + offset = 1; + } + column_types.emplace_back(csv_column_type::repeat_t,depth,offset); + ++p; + break; + } + default: + buffer.clear(); + state = column_state::label; + break; + } + break; + } + case column_state::label: + { + switch (*p) + { + case '*': + { + auto it = type_dictionary.find(buffer); + if (it != type_dictionary.end()) + { + column_types.emplace_back(it->second,depth); + buffer.clear(); + } + else + { + JSONCONS_ASSERT(false); + } + state = column_state::sequence; + break; + } + case ',': + { + auto it = type_dictionary.find(buffer); + if (it != type_dictionary.end()) + { + column_types.emplace_back(it->second,depth); + buffer.clear(); + } + else + { + JSONCONS_ASSERT(false); + } + ++p; + state = column_state::sequence; + break; + } + case ']': + { + JSONCONS_ASSERT(depth > 0); + auto it = type_dictionary.find(buffer); + if (it != type_dictionary.end()) + { + column_types.emplace_back(it->second,depth); + buffer.clear(); + } + else + { + JSONCONS_ASSERT(false); + } + --depth; + ++p; + state = column_state::sequence; + break; + } + default: + { + buffer.push_back(*p); + ++p; + break; + } + } + break; + } + } + } + if (state == column_state::label) + { + auto it = type_dictionary.find(buffer); + if (it != type_dictionary.end()) + { + column_types.emplace_back(it->second,depth); + buffer.clear(); + } + else + { + JSONCONS_ASSERT(false); + } + } +} + +} // detail + +template +class basic_csv_options; + +template +class basic_csv_options_common +{ + friend class basic_csv_options; +public: + using char_type = CharT; + using string_type = std::basic_string; +private: + char_type field_delimiter_; + char_type quote_char_; + char_type quote_escape_char_; + char_type subfield_delimiter_; + + bool enable_nan_to_num_:1; + bool enable_inf_to_num_:1; + bool enable_neginf_to_num_:1; + bool enable_nan_to_str_:1; + bool enable_inf_to_str_:1; + bool enable_neginf_to_str_:1; + bool enable_str_to_nan_:1; + bool enable_str_to_inf_:1; + bool enable_str_to_neginf_:1; + + string_type nan_to_num_; + string_type inf_to_num_; + string_type neginf_to_num_; + string_type nan_to_str_; + string_type inf_to_str_; + string_type neginf_to_str_; + string_type column_names_; + +protected: + basic_csv_options_common() + : field_delimiter_(','), + quote_char_('\"'), + quote_escape_char_('\"'), + subfield_delimiter_(char_type()), + enable_nan_to_num_(false), + enable_inf_to_num_(false), + enable_neginf_to_num_(false), + enable_nan_to_str_(false), + enable_inf_to_str_(false), + enable_neginf_to_str_(false), + enable_str_to_nan_(false), + enable_str_to_inf_(false), + enable_str_to_neginf_(false) + { + } + + basic_csv_options_common(const basic_csv_options_common&) = default; + basic_csv_options_common& operator=(const basic_csv_options_common&) = default; + //basic_csv_options_common& operator=(basic_csv_options_common&&) = default; + + virtual ~basic_csv_options_common() noexcept = default; +public: + + char_type field_delimiter() const + { + return field_delimiter_; + } + + const char_type subfield_delimiter() const + { + return subfield_delimiter_; + } + + char_type quote_char() const + { + return quote_char_; + } + + char_type quote_escape_char() const + { + return quote_escape_char_; + } + + string_type column_names() const + { + return column_names_; + } + + bool enable_nan_to_num() const + { + return enable_nan_to_num_; + } + + bool enable_inf_to_num() const + { + return enable_inf_to_num_; + } + + bool enable_neginf_to_num() const + { + return enable_neginf_to_num_ || enable_inf_to_num_; + } + + bool enable_nan_to_str() const + { + return enable_nan_to_str_; + } + + bool enable_str_to_nan() const + { + return enable_str_to_nan_; + } + + bool enable_inf_to_str() const + { + return enable_inf_to_str_; + } + + bool enable_str_to_inf() const + { + return enable_str_to_inf_; + } + + bool enable_neginf_to_str() const + { + return enable_neginf_to_str_ || enable_inf_to_str_; + } + + bool enable_str_to_neginf() const + { + return enable_str_to_neginf_ || enable_str_to_inf_; + } + + string_type nan_to_num() const + { + return nan_to_num_; + } + + string_type inf_to_num() const + { + return inf_to_num_; + } + + string_type neginf_to_num() const + { + if (enable_neginf_to_num_) + { + return neginf_to_num_; + } + else if (enable_inf_to_num_) + { + string_type s; + s.push_back('-'); + s.append(inf_to_num_); + return s; + } + else + { + return neginf_to_num_; + } + } + + string_type nan_to_str() const + { + return nan_to_str_; + } + + string_type inf_to_str() const + { + return inf_to_str_; + } + + string_type neginf_to_str() const + { + if (enable_neginf_to_str_) + { + return neginf_to_str_; + } + else if (enable_inf_to_str_) + { + string_type s; + s.push_back('-'); + s.append(inf_to_str_); + return s; + } + else + { + return neginf_to_str_; // empty string + } + } +}; + +template +class basic_csv_decode_options : public virtual basic_csv_options_common +{ + friend class basic_csv_options; + using super_type = basic_csv_options_common; +public: + using typename super_type::char_type; + using typename super_type::string_type; + +private: + bool assume_header_:1; + bool ignore_empty_values_:1; + bool ignore_empty_lines_:1; + bool trim_leading_:1; + bool trim_trailing_:1; + bool trim_leading_inside_quotes_:1; + bool trim_trailing_inside_quotes_:1; + bool unquoted_empty_value_is_null_:1; + bool infer_types_:1; + bool lossless_number_:1; + char_type comment_starter_; + csv_mapping_kind mapping_; + std::size_t header_lines_; + std::size_t max_lines_; + string_type column_types_; + string_type column_defaults_; +public: + basic_csv_decode_options() + : assume_header_(false), + ignore_empty_values_(false), + ignore_empty_lines_(true), + trim_leading_(false), + trim_trailing_(false), + trim_leading_inside_quotes_(false), + trim_trailing_inside_quotes_(false), + unquoted_empty_value_is_null_(false), + infer_types_(true), + lossless_number_(false), + comment_starter_('\0'), + mapping_(), + header_lines_(0), + max_lines_((std::numeric_limits::max)()) + {} + + basic_csv_decode_options(const basic_csv_decode_options& other) = default; + + basic_csv_decode_options(basic_csv_decode_options&& other) + : super_type(std::move(other)), + assume_header_(other.assume_header_), + ignore_empty_values_(other.ignore_empty_values_), + ignore_empty_lines_(other.ignore_empty_lines_), + trim_leading_(other.trim_leading_), + trim_trailing_(other.trim_trailing_), + trim_leading_inside_quotes_(other.trim_leading_inside_quotes_), + trim_trailing_inside_quotes_(other.trim_trailing_inside_quotes_), + unquoted_empty_value_is_null_(other.unquoted_empty_value_is_null_), + infer_types_(other.infer_types_), + lossless_number_(other.lossless_number_), + comment_starter_(other.comment_starter_), + mapping_(other.mapping_), + header_lines_(other.header_lines_), + max_lines_(other.max_lines_), + column_types_(std::move(other.column_types_)), + column_defaults_(std::move(other.column_defaults_)) + {} + +protected: + basic_csv_decode_options& operator=(const basic_csv_decode_options& other) = default; + basic_csv_decode_options& operator=(basic_csv_decode_options&& other) = default; +public: + + std::size_t header_lines() const + { + return (assume_header_ && header_lines_ <= 1) ? 1 : header_lines_; + } + + bool assume_header() const + { + return assume_header_; + } + + bool ignore_empty_values() const + { + return ignore_empty_values_; + } + + bool ignore_empty_lines() const + { + return ignore_empty_lines_; + } + + bool trim_leading() const + { + return trim_leading_; + } + + bool trim_trailing() const + { + return trim_trailing_; + } + + bool trim_leading_inside_quotes() const + { + return trim_leading_inside_quotes_; + } + + bool trim_trailing_inside_quotes() const + { + return trim_trailing_inside_quotes_; + } + + bool trim() const + { + return trim_leading_ && trim_trailing_; + } + + bool trim_inside_quotes() const + { + return trim_leading_inside_quotes_ && trim_trailing_inside_quotes_; + } + + bool unquoted_empty_value_is_null() const + { + return unquoted_empty_value_is_null_; + } + + bool infer_types() const + { + return infer_types_; + } + + bool lossless_number() const + { + return lossless_number_; + } + + char_type comment_starter() const + { + return comment_starter_; + } + + csv_mapping_kind mapping_kind() const + { + return mapping_ != csv_mapping_kind() ? mapping_ : (assume_header() || this->column_names().size() > 0 ? csv_mapping_kind::n_objects : csv_mapping_kind::n_rows); + } + + std::size_t max_lines() const + { + return max_lines_; + } + + string_type column_types() const + { + return column_types_; + } + + string_type column_defaults() const + { + return column_defaults_; + } +}; + +template +class basic_csv_encode_options : public virtual basic_csv_options_common +{ + friend class basic_csv_options; + using super_type = basic_csv_options_common; +public: + using typename super_type::char_type; + using typename super_type::string_type; +private: + quote_style_kind quote_style_; + float_chars_format float_format_; + int8_t precision_; + string_type line_delimiter_; +public: + basic_csv_encode_options() + : quote_style_(quote_style_kind::minimal), + float_format_(float_chars_format::general), + precision_(0) + { + line_delimiter_.push_back('\n'); + } + + basic_csv_encode_options(const basic_csv_encode_options& other) = default; + + basic_csv_encode_options(basic_csv_encode_options&& other) + : super_type(std::move(other)), + quote_style_(other.quote_style_), + float_format_(other.float_format_), + precision_(other.precision_), + line_delimiter_(std::move(other.line_delimiter_)) + { + } + +protected: + basic_csv_encode_options& operator=(const basic_csv_encode_options& other) = default; + basic_csv_encode_options& operator=(basic_csv_encode_options&& other) = default; +public: + + quote_style_kind quote_style() const + { + return quote_style_; + } + + float_chars_format float_format() const + { + return float_format_; + } + + int8_t precision() const + { + return precision_; + } + + string_type line_delimiter() const + { + return line_delimiter_; + } +}; + +template +class basic_csv_options final : public basic_csv_decode_options, public basic_csv_encode_options +{ + using char_type = CharT; + using string_type = std::basic_string; + +public: + using basic_csv_decode_options::enable_str_to_nan; + using basic_csv_decode_options::enable_str_to_inf; + using basic_csv_decode_options::enable_str_to_neginf; + using basic_csv_decode_options::nan_to_str; + using basic_csv_decode_options::inf_to_str; + using basic_csv_decode_options::neginf_to_str; + using basic_csv_decode_options::nan_to_num; + using basic_csv_decode_options::inf_to_num; + using basic_csv_decode_options::neginf_to_num; + using basic_csv_decode_options::field_delimiter; + using basic_csv_decode_options::subfield_delimiter; + using basic_csv_decode_options::quote_char; + using basic_csv_decode_options::quote_escape_char; + using basic_csv_decode_options::column_names; + using basic_csv_decode_options::header_lines; + using basic_csv_decode_options::assume_header; + using basic_csv_decode_options::ignore_empty_values; + using basic_csv_decode_options::ignore_empty_lines; + using basic_csv_decode_options::trim_leading; + using basic_csv_decode_options::trim_trailing; + using basic_csv_decode_options::trim_leading_inside_quotes; + using basic_csv_decode_options::trim_trailing_inside_quotes; + using basic_csv_decode_options::trim; + using basic_csv_decode_options::trim_inside_quotes; + using basic_csv_decode_options::unquoted_empty_value_is_null; + using basic_csv_decode_options::infer_types; + using basic_csv_decode_options::lossless_number; + using basic_csv_decode_options::comment_starter; + using basic_csv_decode_options::mapping_kind; + using basic_csv_decode_options::max_lines; + using basic_csv_decode_options::column_types; + using basic_csv_decode_options::column_defaults; + using basic_csv_encode_options::float_format; + using basic_csv_encode_options::precision; + using basic_csv_encode_options::line_delimiter; + using basic_csv_encode_options::quote_style; + + static constexpr size_t default_indent = 4; + +// Constructors + + basic_csv_options() = default; + basic_csv_options(const basic_csv_options&) = default; + basic_csv_options(basic_csv_options&&) = default; + basic_csv_options& operator=(const basic_csv_options&) = default; + basic_csv_options& operator=(basic_csv_options&&) = default; + + basic_csv_options& float_format(float_chars_format value) + { + this->float_format_ = value; + return *this; + } + + basic_csv_options& precision(int8_t value) + { + this->precision_ = value; + return *this; + } + + basic_csv_options& header_lines(std::size_t value) + { + this->header_lines_ = value; + return *this; + } + + basic_csv_options& assume_header(bool value) + { + this->assume_header_ = value; + return *this; + } + + basic_csv_options& ignore_empty_values(bool value) + { + this->ignore_empty_values_ = value; + return *this; + } + + basic_csv_options& ignore_empty_lines(bool value) + { + this->ignore_empty_lines_ = value; + return *this; + } + + basic_csv_options& trim_leading(bool value) + { + this->trim_leading_ = value; + return *this; + } + + basic_csv_options& trim_trailing(bool value) + { + this->trim_trailing_ = value; + return *this; + } + + basic_csv_options& trim_leading_inside_quotes(bool value) + { + this->trim_leading_inside_quotes_ = value; + return *this; + } + + basic_csv_options& trim_trailing_inside_quotes(bool value) + { + this->trim_trailing_inside_quotes_ = value; + return *this; + } + + basic_csv_options& trim(bool value) + { + this->trim_leading_ = value; + this->trim_trailing_ = value; + return *this; + } + + basic_csv_options& trim_inside_quotes(bool value) + { + this->trim_leading_inside_quotes_ = value; + this->trim_trailing_inside_quotes_ = value; + return *this; + } + + basic_csv_options& unquoted_empty_value_is_null(bool value) + { + this->unquoted_empty_value_is_null_ = value; + return *this; + } + + basic_csv_options& column_names(const string_type& value) + { + this->column_names_ = value; + return *this; + } + + basic_csv_options& column_types(const string_type& value) + { + this->column_types_ = value; + return *this; + } + + basic_csv_options& column_defaults(const string_type& value) + { + this->column_defaults_ = value; + return *this; + } + + basic_csv_options& field_delimiter(char_type value) + { + this->field_delimiter_ = value; + return *this; + } + + basic_csv_options& subfield_delimiter(char_type value) + { + this->subfield_delimiter_ = value; + return *this; + } + + basic_csv_options& line_delimiter(const string_type& value) + { + this->line_delimiter_ = value; + return *this; + } + + basic_csv_options& quote_char(char_type value) + { + this->quote_char_ = value; + return *this; + } + + basic_csv_options& infer_types(bool value) + { + this->infer_types_ = value; + return *this; + } + + basic_csv_options& lossless_number(bool value) + { + this->lossless_number_ = value; + return *this; + } + + basic_csv_options& quote_escape_char(char_type value) + { + this->quote_escape_char_ = value; + return *this; + } + + basic_csv_options& comment_starter(char_type value) + { + this->comment_starter_ = value; + return *this; + } + + basic_csv_options& quote_style(quote_style_kind value) + { + this->quote_style_ = value; + return *this; + } + + basic_csv_options& mapping_kind(csv_mapping_kind value) + { + this->mapping_ = value; + return *this; + } + + basic_csv_options& max_lines(std::size_t value) + { + this->max_lines_ = value; + return *this; + } + + basic_csv_options& nan_to_num(const string_type& value) + { + this->enable_nan_to_num_ = true; + this->nan_to_str_.clear(); + this->nan_to_num_ = value; + return *this; + } + + basic_csv_options& inf_to_num(const string_type& value) + { + this->enable_inf_to_num_ = true; + this->inf_to_str_.clear(); + this->inf_to_num_ = value; + return *this; + } + + basic_csv_options& neginf_to_num(const string_type& value) + { + this->enable_neginf_to_num_ = true; + this->neginf_to_str_.clear(); + this->neginf_to_num_ = value; + return *this; + } + + basic_csv_options& nan_to_str(const string_type& value, bool enable_inverse = true) + { + this->enable_nan_to_str_ = true; + this->enable_str_to_nan_ = enable_inverse; + this->nan_to_num_.clear(); + this->nan_to_str_ = value; + return *this; + } + + basic_csv_options& inf_to_str(const string_type& value, bool enable_inverse = true) + { + this->enable_inf_to_str_ = true; + this->enable_inf_to_str_ = enable_inverse; + this->inf_to_num_.clear(); + this->inf_to_str_ = value; + return *this; + } + + basic_csv_options& neginf_to_str(const string_type& value, bool enable_inverse = true) + { + this->enable_neginf_to_str_ = true; + this->enable_neginf_to_str_ = enable_inverse; + this->neginf_to_num_.clear(); + this->neginf_to_str_ = value; + return *this; + } + +}; + +using csv_options = basic_csv_options; +using wcsv_options = basic_csv_options; + +}} +#endif diff --git a/third_party/jsoncons_ext/csv/csv_parser.hpp b/third_party/jsoncons_ext/csv/csv_parser.hpp new file mode 100644 index 0000000000..7fbd20b440 --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv_parser.hpp @@ -0,0 +1,2099 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_PARSER_HPP +#define JSONCONS_CSV_CSV_PARSER_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace csv { + +enum class csv_mode +{ + initial, + header, + data, + subfields +}; + +enum class csv_parse_state +{ + start, + cr, + column_labels, + expect_comment_or_record, + expect_record, + end_record, + no_more_records, + comment, + between_values, + quoted_string, + unquoted_string, + before_unquoted_string, + escaped_value, + minus, + zero, + integer, + fraction, + exp1, + exp2, + exp3, + accept, + before_unquoted_field, + before_unquoted_field_tail, + before_unquoted_field_tail1, + before_last_unquoted_field, + before_last_unquoted_field_tail, + before_unquoted_subfield, + before_unquoted_subfield_tail, + before_quoted_subfield, + before_quoted_subfield_tail, + before_quoted_field, + before_quoted_field_tail, + before_last_quoted_field, + before_last_quoted_field_tail, + done +}; + +enum class cached_state +{ + begin_object, + end_object, + begin_array, + end_array, + name, + item, + done +}; + +struct default_csv_parsing +{ + bool operator()(csv_errc, const ser_context&) noexcept + { + return false; + } +}; + +namespace detail { + + template + class parse_event + { + using temp_allocator_type = TempAllocator; + using string_view_type = typename basic_json_visitor::string_view_type; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using string_type = std::basic_string,char_allocator_type>; + using byte_string_type = basic_byte_string; + + staj_event_type event_type; + string_type string_value; + byte_string_type byte_string_value; + union + { + bool bool_value; + int64_t int64_value; + uint64_t uint64_value; + double double_value; + }; + semantic_tag tag; + public: + parse_event(staj_event_type event_type, semantic_tag tag, const TempAllocator& alloc) + : event_type(event_type), + string_value(alloc), + byte_string_value(alloc), + tag(tag) + { + } + + parse_event(const string_view_type& value, semantic_tag tag, const TempAllocator& alloc) + : event_type(staj_event_type::string_value), + string_value(value.data(),value.length(),alloc), + byte_string_value(alloc), + tag(tag) + { + } + + parse_event(const byte_string_view& value, semantic_tag tag, const TempAllocator& alloc) + : event_type(staj_event_type::byte_string_value), + string_value(alloc), + byte_string_value(value.data(),value.size(),alloc), + tag(tag) + { + } + + parse_event(bool value, semantic_tag tag, const TempAllocator& alloc) + : event_type(staj_event_type::bool_value), + string_value(alloc), + byte_string_value(alloc), + bool_value(value), + tag(tag) + { + } + + parse_event(int64_t value, semantic_tag tag, const TempAllocator& alloc) + : event_type(staj_event_type::int64_value), + string_value(alloc), + byte_string_value(alloc), + int64_value(value), + tag(tag) + { + } + + parse_event(uint64_t value, semantic_tag tag, const TempAllocator& alloc) + : event_type(staj_event_type::uint64_value), + string_value(alloc), + byte_string_value(alloc), + uint64_value(value), + tag(tag) + { + } + + parse_event(double value, semantic_tag tag, const TempAllocator& alloc) + : event_type(staj_event_type::double_value), + string_value(alloc), + byte_string_value(alloc), + double_value(value), + tag(tag) + { + } + + parse_event(const parse_event&) = default; + parse_event(parse_event&&) = default; + parse_event& operator=(const parse_event&) = default; + parse_event& operator=(parse_event&&) = default; + + bool replay(basic_json_visitor& visitor) const + { + switch (event_type) + { + case staj_event_type::begin_array: + return visitor.begin_array(tag, ser_context()); + case staj_event_type::end_array: + return visitor.end_array(ser_context()); + case staj_event_type::string_value: + return visitor.string_value(string_value, tag, ser_context()); + case staj_event_type::byte_string_value: + case staj_event_type::null_value: + return visitor.null_value(tag, ser_context()); + case staj_event_type::bool_value: + return visitor.bool_value(bool_value, tag, ser_context()); + case staj_event_type::int64_value: + return visitor.int64_value(int64_value, tag, ser_context()); + case staj_event_type::uint64_value: + return visitor.uint64_value(uint64_value, tag, ser_context()); + case staj_event_type::double_value: + return visitor.double_value(double_value, tag, ser_context()); + default: + return false; + } + } + }; + + template + class m_columns_filter : public basic_json_visitor + { + public: + using string_view_type = typename basic_json_visitor::string_view_type; + using char_type = CharT; + using temp_allocator_type = TempAllocator; + + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using string_type = std::basic_string,char_allocator_type>; + + using string_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using parse_event_allocator_type = typename std::allocator_traits:: template rebind_alloc>; + using parse_event_vector_type = std::vector, parse_event_allocator_type>; + using parse_event_vector_allocator_type = typename std::allocator_traits:: template rebind_alloc; + private: + TempAllocator alloc_; + std::size_t name_index_; + int level_; + cached_state state_; + std::size_t column_index_; + std::size_t row_index_; + + std::vector column_names_; + std::vector cached_events_; + public: + + m_columns_filter(const TempAllocator& alloc) + : alloc_(alloc), + name_index_(0), + level_(0), + state_(cached_state::begin_object), + column_index_(0), + row_index_(0), + column_names_(alloc), + cached_events_(alloc) + { + } + + void reset() + { + name_index_ = 0; + level_ = 0; + state_ = cached_state::begin_object; + column_index_ = 0; + row_index_ = 0; + column_names_.clear(); + cached_events_.clear(); + } + + bool done() const + { + return state_ == cached_state::done; + } + + void initialize(const std::vector& column_names) + { + for (const auto& name : column_names) + { + column_names_.push_back(name); + cached_events_.emplace_back(alloc_); + } + name_index_ = 0; + level_ = 0; + column_index_ = 0; + row_index_ = 0; + state_ = cached_state::begin_object; + } + + void skip_column() + { + ++name_index_; + } + + bool replay_parse_events(basic_json_visitor& visitor) + { + bool more = true; + while (more) + { + switch (state_) + { + case cached_state::begin_object: + more = visitor.begin_object(semantic_tag::none, ser_context()); + column_index_ = 0; + state_ = cached_state::name; + break; + case cached_state::end_object: + more = visitor.end_object(ser_context()); + state_ = cached_state::done; + break; + case cached_state::name: + if (column_index_ < column_names_.size()) + { + more = visitor.key(column_names_[column_index_], ser_context()); + state_ = cached_state::begin_array; + } + else + { + state_ = cached_state::end_object; + } + break; + case cached_state::begin_array: + more = visitor.begin_array(semantic_tag::none, ser_context()); + row_index_ = 0; + state_ = cached_state::item; + break; + case cached_state::end_array: + more = visitor.end_array(ser_context()); + ++column_index_; + state_ = cached_state::name; + break; + case cached_state::item: + if (row_index_ < cached_events_[column_index_].size()) + { + more = cached_events_[column_index_][row_index_].replay(visitor); + ++row_index_; + } + else + { + state_ = cached_state::end_array; + } + break; + default: + more = false; + break; + } + } + return more; + } + + void visit_flush() override + { + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override + { + ec = csv_errc::invalid_parse_state; + return false; + } + + bool visit_end_object(const ser_context&, std::error_code& ec) override + { + ec = csv_errc::invalid_parse_state; + return false; + } + + bool visit_begin_array(semantic_tag tag, const ser_context&, std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(staj_event_type::begin_array, tag, alloc_); + + ++level_; + } + return true; + } + + bool visit_end_array(const ser_context&, std::error_code&) override + { + if (level_ > 0) + { + cached_events_[name_index_].emplace_back(staj_event_type::end_array, semantic_tag::none, alloc_); + ++name_index_; + --level_; + } + else + { + name_index_ = 0; + } + return true; + } + + bool visit_key(const string_view_type&, const ser_context&, std::error_code& ec) override + { + ec = csv_errc::invalid_parse_state; + return false; + } + + bool visit_null(semantic_tag tag, const ser_context&, std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(staj_event_type::null_value, tag, alloc_); + if (level_ == 0) + { + ++name_index_; + } + } + return true; + } + + bool visit_string(const string_view_type& value, semantic_tag tag, const ser_context&, std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(value, tag, alloc_); + + if (level_ == 0) + { + ++name_index_; + } + } + return true; + } + + bool visit_byte_string(const byte_string_view& value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(value, tag, alloc_); + if (level_ == 0) + { + ++name_index_; + } + } + return true; + } + + bool visit_double(double value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(value, tag, alloc_); + if (level_ == 0) + { + ++name_index_; + } + } + return true; + } + + bool visit_int64(int64_t value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(value, tag, alloc_); + if (level_ == 0) + { + ++name_index_; + } + } + return true; + } + + bool visit_uint64(uint64_t value, + semantic_tag tag, + const ser_context&, + std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(value, tag, alloc_); + if (level_ == 0) + { + ++name_index_; + } + } + return true; + } + + bool visit_bool(bool value, semantic_tag tag, const ser_context&, std::error_code&) override + { + if (name_index_ < column_names_.size()) + { + cached_events_[name_index_].emplace_back(value, tag, alloc_); + if (level_ == 0) + { + ++name_index_; + } + } + return true; + } + }; + +} // namespace detail + +template > +class basic_csv_parser : public ser_context +{ +public: + using string_view_type = jsoncons::basic_string_view; + using char_type = CharT; +private: + struct string_maps_to_double + { + string_view_type s; + + bool operator()(const std::pair& val) const + { + return val.first == s; + } + }; + + using temp_allocator_type = TempAllocator; + typedef typename std::allocator_traits:: template rebind_alloc char_allocator_type; + using string_type = std::basic_string,char_allocator_type>; + typedef typename std::allocator_traits:: template rebind_alloc string_allocator_type; + typedef typename std::allocator_traits:: template rebind_alloc csv_mode_allocator_type; + typedef typename std::allocator_traits:: template rebind_alloc csv_type_info_allocator_type; + typedef typename std::allocator_traits:: template rebind_alloc> string_vector_allocator_type; + typedef typename std::allocator_traits:: template rebind_alloc csv_parse_state_allocator_type; + + static constexpr int default_depth = 3; + + temp_allocator_type alloc_; + csv_parse_state state_; + basic_json_visitor* visitor_; + std::function err_handler_; + std::size_t column_; + std::size_t line_; + int depth_; + const basic_csv_decode_options options_; + std::size_t column_index_; + std::size_t level_; + std::size_t offset_; + jsoncons::detail::chars_to to_double_; + const CharT* begin_input_; + const CharT* input_end_; + const CharT* input_ptr_; + bool more_; + std::size_t header_line_; + + detail::m_columns_filter m_columns_filter_; + std::vector stack_; + std::vector column_names_; + std::vector column_types_; + std::vector column_defaults_; + std::vector state_stack_; + string_type buffer_; + std::vector,double>> string_double_map_; + +public: + basic_csv_parser(const TempAllocator& alloc = TempAllocator()) + : basic_csv_parser(basic_csv_decode_options(), + default_csv_parsing(), + alloc) + { + } + + basic_csv_parser(const basic_csv_decode_options& options, + const TempAllocator& alloc = TempAllocator()) + : basic_csv_parser(options, + default_csv_parsing(), + alloc) + { + } + + basic_csv_parser(std::function err_handler, + const TempAllocator& alloc = TempAllocator()) + : basic_csv_parser(basic_csv_decode_options(), + err_handler, + alloc) + { + } + + basic_csv_parser(const basic_csv_decode_options& options, + std::function err_handler, + const TempAllocator& alloc = TempAllocator()) + : alloc_(alloc), + state_(csv_parse_state::start), + visitor_(nullptr), + err_handler_(err_handler), + column_(1), + line_(1), + depth_(default_depth), + options_(options), + column_index_(0), + level_(0), + offset_(0), + begin_input_(nullptr), + input_end_(nullptr), + input_ptr_(nullptr), + more_(true), + header_line_(1), + m_columns_filter_(alloc), + stack_(alloc), + column_names_(alloc), + column_types_(alloc), + column_defaults_(alloc), + state_stack_(alloc), + buffer_(alloc) + { + if (options_.enable_str_to_nan()) + { + string_double_map_.emplace_back(options_.nan_to_str(),std::nan("")); + } + if (options_.enable_str_to_inf()) + { + string_double_map_.emplace_back(options_.inf_to_str(),std::numeric_limits::infinity()); + } + if (options_.enable_str_to_neginf()) + { + string_double_map_.emplace_back(options_.neginf_to_str(),-std::numeric_limits::infinity()); + } + + initialize(); + } + + ~basic_csv_parser() noexcept + { + } + + bool done() const + { + return state_ == csv_parse_state::done; + } + + bool accept() const + { + return state_ == csv_parse_state::accept || state_ == csv_parse_state::done; + } + + bool stopped() const + { + return !more_; + } + + bool source_exhausted() const + { + return input_ptr_ == input_end_; + } + + const std::vector& column_labels() const + { + return column_names_; + } + + void reinitialize() + { + state_ = csv_parse_state::start; + visitor_ = nullptr; + column_ = 1; + line_ = 1; + depth_ = default_depth; + column_index_ = 0; + level_ = 0; + offset_ = 0; + begin_input_ = nullptr; + input_end_ = nullptr; + input_ptr_ = nullptr; + more_ = true; + header_line_ = 1; + m_columns_filter_.reset(); + stack_.clear(); + column_names_.clear(); + column_types_.clear(); + column_defaults_.clear(); + state_stack_.clear(); + buffer_.clear(); + + initialize(); + } + + void restart() + { + more_ = true; + } + + void parse_some(basic_json_visitor& visitor) + { + std::error_code ec; + parse_some(visitor,ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line_,column_)); + } + } + + void parse_some(basic_json_visitor& visitor, std::error_code& ec) + { + switch (options_.mapping_kind()) + { + case csv_mapping_kind::m_columns: + visitor_ = &m_columns_filter_; + break; + default: + visitor_ = std::addressof(visitor); + break; + } + + const CharT* local_input_end = input_end_; + + if (input_ptr_ == local_input_end && more_) + { + switch (state_) + { + case csv_parse_state::start: + ec = csv_errc::source_error; + more_ = false; + return; + case csv_parse_state::before_unquoted_field: + case csv_parse_state::before_last_unquoted_field: + end_unquoted_string_value(ec); + state_ = csv_parse_state::before_last_unquoted_field_tail; + break; + case csv_parse_state::before_last_unquoted_field_tail: + if (stack_.back() == csv_mode::subfields) + { + stack_.pop_back(); + more_ = visitor_->end_array(*this, ec); + } + ++column_index_; + state_ = csv_parse_state::end_record; + break; + case csv_parse_state::before_unquoted_string: + buffer_.clear(); + JSONCONS_FALLTHROUGH; + case csv_parse_state::unquoted_string: + if (options_.trim_leading() || options_.trim_trailing()) + { + trim_string_buffer(options_.trim_leading(),options_.trim_trailing()); + } + if (options_.ignore_empty_values() && buffer_.empty()) + { + state_ = csv_parse_state::end_record; + } + else + { + before_value(ec); + state_ = csv_parse_state::before_unquoted_field; + } + break; + case csv_parse_state::before_last_quoted_field: + end_quoted_string_value(ec); + ++column_index_; + state_ = csv_parse_state::end_record; + break; + case csv_parse_state::escaped_value: + if (options_.quote_escape_char() == options_.quote_char()) + { + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + before_value(ec); + ++column_; + state_ = csv_parse_state::before_last_quoted_field; + } + else + { + state_ = csv_parse_state::end_record; + } + } + else + { + ec = csv_errc::invalid_escaped_char; + more_ = false; + return; + } + break; + case csv_parse_state::end_record: + if (column_index_ > 0) + { + after_record(ec); + } + state_ = csv_parse_state::no_more_records; + break; + case csv_parse_state::no_more_records: + switch (stack_.back()) + { + case csv_mode::header: + stack_.pop_back(); + break; + case csv_mode::data: + stack_.pop_back(); + break; + default: + break; + } + more_ = visitor_->end_array(*this, ec); + if (options_.mapping_kind() == csv_mapping_kind::m_columns) + { + if (!m_columns_filter_.done()) + { + more_ = m_columns_filter_.replay_parse_events(visitor); + } + else + { + state_ = csv_parse_state::accept; + } + } + else + { + state_ = csv_parse_state::accept; + } + break; + case csv_parse_state::accept: + if (!(stack_.size() == 1 && stack_.back() == csv_mode::initial)) + { + err_handler_(csv_errc::unexpected_eof, *this); + ec = csv_errc::unexpected_eof; + more_ = false; + return; + } + stack_.pop_back(); + visitor_->flush(); + state_ = csv_parse_state::done; + more_ = false; + return; + default: + state_ = csv_parse_state::end_record; + break; + } + } + + for (; (input_ptr_ < local_input_end) && more_;) + { + CharT curr_char = *input_ptr_; + + switch (state_) + { + case csv_parse_state::cr: + ++line_; + column_ = 1; + switch (*input_ptr_) + { + case '\n': + ++input_ptr_; + state_ = pop_state(); + break; + default: + state_ = pop_state(); + break; + } + break; + case csv_parse_state::start: + if (options_.mapping_kind() != csv_mapping_kind::m_columns) + { + more_ = visitor_->begin_array(semantic_tag::none, *this, ec); + } + if (options_.assume_header() && options_.mapping_kind() == csv_mapping_kind::n_rows && options_.column_names().size() > 0) + { + column_index_ = 0; + state_ = csv_parse_state::column_labels; + more_ = visitor_->begin_array(semantic_tag::none, *this, ec); + state_ = csv_parse_state::expect_comment_or_record; + } + else + { + state_ = csv_parse_state::expect_comment_or_record; + } + break; + case csv_parse_state::column_labels: + if (column_index_ < column_names_.size()) + { + more_ = visitor_->string_value(column_names_[column_index_], semantic_tag::none, *this, ec); + ++column_index_; + } + else + { + more_ = visitor_->end_array(*this, ec); + state_ = csv_parse_state::expect_comment_or_record; + //stack_.back() = csv_mode::data; + column_index_ = 0; + } + break; + case csv_parse_state::comment: + switch (curr_char) + { + case '\n': + { + ++line_; + if (stack_.back() == csv_mode::header) + { + ++header_line_; + } + column_ = 1; + state_ = csv_parse_state::expect_comment_or_record; + break; + } + case '\r': + ++line_; + if (stack_.back() == csv_mode::header) + { + ++header_line_; + } + column_ = 1; + state_ = csv_parse_state::expect_comment_or_record; + push_state(state_); + state_ = csv_parse_state::cr; + break; + default: + ++column_; + break; + } + ++input_ptr_; + break; + + case csv_parse_state::expect_comment_or_record: + buffer_.clear(); + if (curr_char == options_.comment_starter()) + { + state_ = csv_parse_state::comment; + ++column_; + ++input_ptr_; + } + else + { + state_ = csv_parse_state::expect_record; + } + break; + case csv_parse_state::quoted_string: + { + if (curr_char == options_.quote_escape_char()) + { + state_ = csv_parse_state::escaped_value; + } + else if (curr_char == options_.quote_char()) + { + state_ = csv_parse_state::between_values; + } + else + { + buffer_.push_back(static_cast(curr_char)); + } + } + ++column_; + ++input_ptr_; + break; + case csv_parse_state::escaped_value: + { + if (curr_char == options_.quote_char()) + { + buffer_.push_back(static_cast(curr_char)); + state_ = csv_parse_state::quoted_string; + ++column_; + ++input_ptr_; + } + else if (options_.quote_escape_char() == options_.quote_char()) + { + state_ = csv_parse_state::between_values; + } + else + { + ec = csv_errc::invalid_escaped_char; + more_ = false; + return; + } + } + break; + case csv_parse_state::between_values: + switch (curr_char) + { + case '\r': + case '\n': + { + if (options_.trim_leading() || options_.trim_trailing()) + { + trim_string_buffer(options_.trim_leading(),options_.trim_trailing()); + } + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + before_value(ec); + state_ = csv_parse_state::before_last_quoted_field; + } + else + { + state_ = csv_parse_state::end_record; + } + break; + } + default: + if (curr_char == options_.field_delimiter()) + { + if (options_.trim_leading() || options_.trim_trailing()) + { + trim_string_buffer(options_.trim_leading(),options_.trim_trailing()); + } + before_value(ec); + state_ = csv_parse_state::before_quoted_field; + } + else if (options_.subfield_delimiter() != char_type() && curr_char == options_.subfield_delimiter()) + { + if (options_.trim_leading() || options_.trim_trailing()) + { + trim_string_buffer(options_.trim_leading(),options_.trim_trailing()); + } + before_value(ec); + state_ = csv_parse_state::before_quoted_subfield; + } + else if (curr_char == ' ' || curr_char == '\t') + { + ++column_; + ++input_ptr_; + } + else + { + ec = csv_errc::unexpected_char_between_fields; + more_ = false; + return; + } + break; + } + break; + case csv_parse_state::before_unquoted_string: + { + buffer_.clear(); + state_ = csv_parse_state::unquoted_string; + break; + } + case csv_parse_state::before_unquoted_field: + end_unquoted_string_value(ec); + state_ = csv_parse_state::before_unquoted_field_tail; + break; + case csv_parse_state::before_unquoted_field_tail: + { + if (stack_.back() == csv_mode::subfields) + { + stack_.pop_back(); + more_ = visitor_->end_array(*this, ec); + } + ++column_index_; + state_ = csv_parse_state::before_unquoted_string; + ++column_; + ++input_ptr_; + break; + } + case csv_parse_state::before_unquoted_field_tail1: + { + if (stack_.back() == csv_mode::subfields) + { + stack_.pop_back(); + more_ = visitor_->end_array(*this, ec); + } + state_ = csv_parse_state::end_record; + ++column_; + ++input_ptr_; + break; + } + + case csv_parse_state::before_last_unquoted_field: + end_unquoted_string_value(ec); + state_ = csv_parse_state::before_last_unquoted_field_tail; + break; + + case csv_parse_state::before_last_unquoted_field_tail: + if (stack_.back() == csv_mode::subfields) + { + stack_.pop_back(); + more_ = visitor_->end_array(*this, ec); + } + ++column_index_; + state_ = csv_parse_state::end_record; + break; + + case csv_parse_state::before_unquoted_subfield: + if (stack_.back() == csv_mode::data) + { + stack_.push_back(csv_mode::subfields); + more_ = visitor_->begin_array(semantic_tag::none, *this, ec); + } + state_ = csv_parse_state::before_unquoted_subfield_tail; + break; + case csv_parse_state::before_unquoted_subfield_tail: + end_unquoted_string_value(ec); + state_ = csv_parse_state::before_unquoted_string; + ++column_; + ++input_ptr_; + break; + case csv_parse_state::before_quoted_field: + end_quoted_string_value(ec); + state_ = csv_parse_state::before_unquoted_field_tail; // return to unquoted + break; + case csv_parse_state::before_quoted_subfield: + if (stack_.back() == csv_mode::data) + { + stack_.push_back(csv_mode::subfields); + more_ = visitor_->begin_array(semantic_tag::none, *this, ec); + } + state_ = csv_parse_state::before_quoted_subfield_tail; + break; + case csv_parse_state::before_quoted_subfield_tail: + end_quoted_string_value(ec); + state_ = csv_parse_state::before_unquoted_string; + ++column_; + ++input_ptr_; + break; + case csv_parse_state::before_last_quoted_field: + end_quoted_string_value(ec); + state_ = csv_parse_state::before_last_quoted_field_tail; + break; + case csv_parse_state::before_last_quoted_field_tail: + if (stack_.back() == csv_mode::subfields) + { + stack_.pop_back(); + more_ = visitor_->end_array(*this, ec); + } + ++column_index_; + state_ = csv_parse_state::end_record; + break; + case csv_parse_state::unquoted_string: + { + switch (curr_char) + { + case '\n': + case '\r': + { + if (options_.trim_leading() || options_.trim_trailing()) + { + trim_string_buffer(options_.trim_leading(),options_.trim_trailing()); + } + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + before_value(ec); + state_ = csv_parse_state::before_last_unquoted_field; + } + else + { + state_ = csv_parse_state::end_record; + } + break; + } + default: + if (curr_char == options_.field_delimiter()) + { + if (options_.trim_leading() || options_.trim_trailing()) + { + trim_string_buffer(options_.trim_leading(),options_.trim_trailing()); + } + before_value(ec); + state_ = csv_parse_state::before_unquoted_field; + } + else if (options_.subfield_delimiter() != char_type() && curr_char == options_.subfield_delimiter()) + { + if (options_.trim_leading() || options_.trim_trailing()) + { + trim_string_buffer(options_.trim_leading(),options_.trim_trailing()); + } + before_value(ec); + state_ = csv_parse_state::before_unquoted_subfield; + } + else if (curr_char == options_.quote_char()) + { + buffer_.clear(); + state_ = csv_parse_state::quoted_string; + ++column_; + ++input_ptr_; + } + else + { + buffer_.push_back(static_cast(curr_char)); + ++column_; + ++input_ptr_; + } + break; + } + break; + } + case csv_parse_state::expect_record: + { + switch (curr_char) + { + case '\n': + { + if (!options_.ignore_empty_lines()) + { + before_record(ec); + state_ = csv_parse_state::end_record; + } + else + { + ++line_; + column_ = 1; + state_ = csv_parse_state::expect_comment_or_record; + ++input_ptr_; + } + break; + } + case '\r': + if (!options_.ignore_empty_lines()) + { + before_record(ec); + state_ = csv_parse_state::end_record; + } + else + { + ++line_; + column_ = 1; + state_ = csv_parse_state::expect_comment_or_record; + ++input_ptr_; + push_state(state_); + state_ = csv_parse_state::cr; + } + break; + case ' ': + case '\t': + if (!options_.trim_leading()) + { + buffer_.push_back(static_cast(curr_char)); + before_record(ec); + state_ = csv_parse_state::unquoted_string; + } + ++column_; + ++input_ptr_; + break; + default: + before_record(ec); + if (curr_char == options_.quote_char()) + { + buffer_.clear(); + state_ = csv_parse_state::quoted_string; + ++column_; + ++input_ptr_; + } + else + { + state_ = csv_parse_state::unquoted_string; + } + break; + } + break; + } + case csv_parse_state::end_record: + { + switch (curr_char) + { + case '\n': + { + ++line_; + column_ = 1; + state_ = csv_parse_state::expect_comment_or_record; + after_record(ec); + ++input_ptr_; + break; + } + case '\r': + ++line_; + column_ = 1; + state_ = csv_parse_state::expect_comment_or_record; + after_record(ec); + push_state(state_); + state_ = csv_parse_state::cr; + ++input_ptr_; + break; + case ' ': + case '\t': + ++column_; + ++input_ptr_; + break; + default: + err_handler_(csv_errc::syntax_error, *this); + ec = csv_errc::syntax_error; + more_ = false; + return; + } + break; + } + default: + err_handler_(csv_errc::invalid_parse_state, *this); + ec = csv_errc::invalid_parse_state; + more_ = false; + return; + } + if (line_ > options_.max_lines()) + { + state_ = csv_parse_state::done; + more_ = false; + } + } + } + + void finish_parse() + { + std::error_code ec; + finish_parse(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line_,column_)); + } + } + + void finish_parse(std::error_code& ec) + { + while (more_) + { + parse_some(ec); + } + } + + csv_parse_state state() const + { + return state_; + } + + void update(const string_view_type sv) + { + update(sv.data(),sv.length()); + } + + void update(const CharT* data, std::size_t length) + { + begin_input_ = data; + input_end_ = data + length; + input_ptr_ = begin_input_; + } + + std::size_t line() const override + { + return line_; + } + + std::size_t column() const override + { + return column_; + } + +private: + void initialize() + { + jsoncons::csv::detail::parse_column_names(options_.column_names(), column_names_); + jsoncons::csv::detail::parse_column_types(options_.column_types(), column_types_); + jsoncons::csv::detail::parse_column_names(options_.column_defaults(), column_defaults_); + + stack_.reserve(default_depth); + stack_.push_back(csv_mode::initial); + stack_.push_back((options_.header_lines() > 0) ? csv_mode::header + : csv_mode::data); + } + + // name + void before_value(std::error_code& ec) + { + switch (stack_.back()) + { + case csv_mode::header: + if (options_.trim_leading_inside_quotes() || options_.trim_trailing_inside_quotes()) + { + trim_string_buffer(options_.trim_leading_inside_quotes(),options_.trim_trailing_inside_quotes()); + } + if (line_ == header_line_) + { + column_names_.push_back(buffer_); + if (options_.assume_header() && options_.mapping_kind() == csv_mapping_kind::n_rows) + { + more_ = visitor_->string_value(buffer_, semantic_tag::none, *this, ec); + } + } + break; + case csv_mode::data: + if (options_.mapping_kind() == csv_mapping_kind::n_objects) + { + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + if (column_index_ < column_names_.size() + offset_) + { + more_ = visitor_->key(column_names_[column_index_ - offset_], *this, ec); + } + } + } + break; + default: + break; + } + } + + // begin_array or begin_record + void before_record(std::error_code& ec) + { + offset_ = 0; + + switch (stack_.back()) + { + case csv_mode::header: + if (options_.assume_header() && line_ == header_line_) + { + if (options_.mapping_kind() == csv_mapping_kind::n_rows) + { + more_ = visitor_->begin_array(semantic_tag::none, *this, ec); + } + } + break; + case csv_mode::data: + switch (options_.mapping_kind()) + { + case csv_mapping_kind::n_rows: + more_ = visitor_->begin_array(semantic_tag::none, *this, ec); + break; + case csv_mapping_kind::n_objects: + more_ = visitor_->begin_object(semantic_tag::none, *this, ec); + break; + case csv_mapping_kind::m_columns: + break; + default: + break; + } + break; + default: + break; + } + } + + // end_array, begin_array, string_value (headers) + void after_record(std::error_code& ec) + { + if (column_types_.size() > 0) + { + if (level_ > 0) + { + more_ = visitor_->end_array(*this, ec); + level_ = 0; + } + } + switch (stack_.back()) + { + case csv_mode::header: + if (line_ >= options_.header_lines()) + { + stack_.back() = csv_mode::data; + } + switch (options_.mapping_kind()) + { + case csv_mapping_kind::n_rows: + if (options_.assume_header()) + { + more_ = visitor_->end_array(*this, ec); + } + break; + case csv_mapping_kind::m_columns: + m_columns_filter_.initialize(column_names_); + break; + default: + break; + } + break; + case csv_mode::data: + case csv_mode::subfields: + { + switch (options_.mapping_kind()) + { + case csv_mapping_kind::n_rows: + more_ = visitor_->end_array(*this, ec); + break; + case csv_mapping_kind::n_objects: + more_ = visitor_->end_object(*this, ec); + break; + case csv_mapping_kind::m_columns: + more_ = visitor_->end_array(*this, ec); + break; + } + break; + } + default: + break; + } + column_index_ = 0; + } + + void trim_string_buffer(bool trim_leading, bool trim_trailing) + { + std::size_t start = 0; + std::size_t length = buffer_.length(); + if (trim_leading) + { + bool done = false; + while (!done && start < buffer_.length()) + { + if ((buffer_[start] < 256) && std::isspace(buffer_[start])) + { + ++start; + } + else + { + done = true; + } + } + } + if (trim_trailing) + { + bool done = false; + while (!done && length > 0) + { + if ((buffer_[length-1] < 256) && std::isspace(buffer_[length-1])) + { + --length; + } + else + { + done = true; + } + } + } + if (start != 0 || length != buffer_.size()) + { + // Do not use buffer_.substr(...), as this won't preserve the allocator state. + buffer_.resize(length); + buffer_.erase(0, start); + } + } + + /* + end_array, begin_array, xxx_value (end_value) + */ + void end_unquoted_string_value(std::error_code& ec) + { + switch (stack_.back()) + { + case csv_mode::data: + case csv_mode::subfields: + switch (options_.mapping_kind()) + { + case csv_mapping_kind::n_rows: + if (options_.unquoted_empty_value_is_null() && buffer_.length() == 0) + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + else + { + end_value(options_.infer_types(), ec); + } + break; + case csv_mapping_kind::n_objects: + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + if (column_index_ < column_names_.size() + offset_) + { + if (options_.unquoted_empty_value_is_null() && buffer_.length() == 0) + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + else + { + end_value(options_.infer_types(), ec); + } + } + else if (level_ > 0) + { + if (options_.unquoted_empty_value_is_null() && buffer_.length() == 0) + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + else + { + end_value(options_.infer_types(), ec); + } + } + } + break; + case csv_mapping_kind::m_columns: + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + end_value(options_.infer_types(), ec); + } + else + { + m_columns_filter_.skip_column(); + } + break; + } + break; + default: + break; + } + } + + void end_quoted_string_value(std::error_code& ec) + { + switch (stack_.back()) + { + case csv_mode::data: + case csv_mode::subfields: + if (options_.trim_leading_inside_quotes() || options_.trim_trailing_inside_quotes()) + { + trim_string_buffer(options_.trim_leading_inside_quotes(),options_.trim_trailing_inside_quotes()); + } + switch (options_.mapping_kind()) + { + case csv_mapping_kind::n_rows: + end_value(false, ec); + break; + case csv_mapping_kind::n_objects: + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + if (column_index_ < column_names_.size() + offset_) + { + if (options_.unquoted_empty_value_is_null() && buffer_.length() == 0) + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + else + { + end_value(false, ec); + } + } + else if (level_ > 0) + { + if (options_.unquoted_empty_value_is_null() && buffer_.length() == 0) + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + else + { + end_value(false, ec); + } + } + } + break; + case csv_mapping_kind::m_columns: + if (!(options_.ignore_empty_values() && buffer_.empty())) + { + end_value(false, ec); + } + else + { + m_columns_filter_.skip_column(); + } + break; + } + break; + default: + break; + } + } + + void end_value(bool infer_types, std::error_code& ec) + { + auto it = std::find_if(string_double_map_.begin(), string_double_map_.end(), string_maps_to_double{ buffer_ }); + if (it != string_double_map_.end()) + { + more_ = visitor_->double_value(it->second, semantic_tag::none, *this, ec); + } + else if (column_index_ < column_types_.size() + offset_) + { + if (column_types_[column_index_ - offset_].col_type == csv_column_type::repeat_t) + { + offset_ = offset_ + column_types_[column_index_ - offset_].rep_count; + if (column_index_ - offset_ + 1 < column_types_.size()) + { + if (column_index_ == offset_ || level_ > column_types_[column_index_-offset_].level) + { + more_ = visitor_->end_array(*this, ec); + } + level_ = column_index_ == offset_ ? 0 : column_types_[column_index_ - offset_].level; + } + } + if (level_ < column_types_[column_index_ - offset_].level) + { + more_ = visitor_->begin_array(semantic_tag::none, *this, ec); + level_ = column_types_[column_index_ - offset_].level; + } + else if (level_ > column_types_[column_index_ - offset_].level) + { + more_ = visitor_->end_array(*this, ec); + level_ = column_types_[column_index_ - offset_].level; + } + switch (column_types_[column_index_ - offset_].col_type) + { + case csv_column_type::integer_t: + { + std::basic_istringstream,char_allocator_type> iss{buffer_}; + int64_t val; + iss >> val; + if (!iss.fail()) + { + more_ = visitor_->int64_value(val, semantic_tag::none, *this, ec); + } + else + { + if (column_index_ - offset_ < column_defaults_.size() && column_defaults_[column_index_ - offset_].length() > 0) + { + basic_json_parser parser(alloc_); + parser.update(column_defaults_[column_index_ - offset_].data(),column_defaults_[column_index_ - offset_].length()); + parser.parse_some(*visitor_); + parser.finish_parse(*visitor_); + } + else + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + } + } + break; + case csv_column_type::float_t: + { + if (options_.lossless_number()) + { + more_ = visitor_->string_value(buffer_,semantic_tag::bigdec, *this, ec); + } + else + { + std::basic_istringstream, char_allocator_type> iss{ buffer_ }; + double val; + iss >> val; + if (!iss.fail()) + { + more_ = visitor_->double_value(val, semantic_tag::none, *this, ec); + } + else + { + if (column_index_ - offset_ < column_defaults_.size() && column_defaults_[column_index_ - offset_].length() > 0) + { + basic_json_parser parser(alloc_); + parser.update(column_defaults_[column_index_ - offset_].data(),column_defaults_[column_index_ - offset_].length()); + parser.parse_some(*visitor_); + parser.finish_parse(*visitor_); + } + else + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + } + } + } + break; + case csv_column_type::boolean_t: + { + if (buffer_.length() == 1 && buffer_[0] == '0') + { + more_ = visitor_->bool_value(false, semantic_tag::none, *this, ec); + } + else if (buffer_.length() == 1 && buffer_[0] == '1') + { + more_ = visitor_->bool_value(true, semantic_tag::none, *this, ec); + } + else if (buffer_.length() == 5 && ((buffer_[0] == 'f' || buffer_[0] == 'F') && (buffer_[1] == 'a' || buffer_[1] == 'A') && (buffer_[2] == 'l' || buffer_[2] == 'L') && (buffer_[3] == 's' || buffer_[3] == 'S') && (buffer_[4] == 'e' || buffer_[4] == 'E'))) + { + more_ = visitor_->bool_value(false, semantic_tag::none, *this, ec); + } + else if (buffer_.length() == 4 && ((buffer_[0] == 't' || buffer_[0] == 'T') && (buffer_[1] == 'r' || buffer_[1] == 'R') && (buffer_[2] == 'u' || buffer_[2] == 'U') && (buffer_[3] == 'e' || buffer_[3] == 'E'))) + { + more_ = visitor_->bool_value(true, semantic_tag::none, *this, ec); + } + else + { + if (column_index_ - offset_ < column_defaults_.size() && column_defaults_[column_index_ - offset_].length() > 0) + { + basic_json_parser parser(alloc_); + parser.update(column_defaults_[column_index_ - offset_].data(),column_defaults_[column_index_ - offset_].length()); + parser.parse_some(*visitor_); + parser.finish_parse(*visitor_); + } + else + { + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + } + } + } + break; + default: + if (buffer_.length() > 0) + { + more_ = visitor_->string_value(buffer_, semantic_tag::none, *this, ec); + } + else + { + if (column_index_ < column_defaults_.size() + offset_ && column_defaults_[column_index_ - offset_].length() > 0) + { + basic_json_parser parser(alloc_); + parser.update(column_defaults_[column_index_ - offset_].data(),column_defaults_[column_index_ - offset_].length()); + parser.parse_some(*visitor_); + parser.finish_parse(*visitor_); + } + else + { + more_ = visitor_->string_value(string_view_type(), semantic_tag::none, *this, ec); + } + } + break; + } + } + else + { + if (infer_types) + { + end_value_with_numeric_check(ec); + } + else + { + more_ = visitor_->string_value(buffer_, semantic_tag::none, *this, ec); + } + } + } + + enum class numeric_check_state + { + initial, + null, + boolean_true, + boolean_false, + minus, + zero, + integer, + fraction1, + fraction, + exp1, + exp, + not_a_number + }; + + /* + xxx_value + */ + void end_value_with_numeric_check(std::error_code& ec) + { + numeric_check_state state = numeric_check_state::initial; + bool is_negative = false; + //int precision = 0; + //uint8_t decimal_places = 0; + + auto last = buffer_.end(); + + std::string buffer; + for (auto p = buffer_.begin(); state != numeric_check_state::not_a_number && p != last; ++p) + { + switch (state) + { + case numeric_check_state::initial: + { + switch (*p) + { + case 'n':case 'N': + if ((last-p) == 4 && (p[1] == 'u' || p[1] == 'U') && (p[2] == 'l' || p[2] == 'L') && (p[3] == 'l' || p[3] == 'L')) + { + state = numeric_check_state::null; + } + else + { + state = numeric_check_state::not_a_number; + } + break; + case 't':case 'T': + if ((last-p) == 4 && (p[1] == 'r' || p[1] == 'R') && (p[2] == 'u' || p[2] == 'U') && (p[3] == 'e' || p[3] == 'U')) + { + state = numeric_check_state::boolean_true; + } + else + { + state = numeric_check_state::not_a_number; + } + break; + case 'f':case 'F': + if ((last-p) == 5 && (p[1] == 'a' || p[1] == 'A') && (p[2] == 'l' || p[2] == 'L') && (p[3] == 's' || p[3] == 'S') && (p[4] == 'e' || p[4] == 'E')) + { + state = numeric_check_state::boolean_false; + } + else + { + state = numeric_check_state::not_a_number; + } + break; + case '-': + is_negative = true; + buffer.push_back(*p); + state = numeric_check_state::minus; + break; + case '0': + //++precision; + buffer.push_back(*p); + state = numeric_check_state::zero; + break; + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + //++precision; + buffer.push_back(*p); + state = numeric_check_state::integer; + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + case numeric_check_state::zero: + { + switch (*p) + { + case '.': + buffer.push_back(to_double_.get_decimal_point()); + state = numeric_check_state::fraction1; + break; + case 'e':case 'E': + buffer.push_back(*p); + state = numeric_check_state::exp1; + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + case numeric_check_state::integer: + { + switch (*p) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + //++precision; + buffer.push_back(*p); + break; + case '.': + buffer.push_back(to_double_.get_decimal_point()); + state = numeric_check_state::fraction1; + break; + case 'e':case 'E': + buffer.push_back(*p); + state = numeric_check_state::exp1; + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + case numeric_check_state::minus: + { + switch (*p) + { + case '0': + //++precision; + buffer.push_back(*p); + state = numeric_check_state::zero; + break; + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + //++precision; + buffer.push_back(*p); + state = numeric_check_state::integer; + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + case numeric_check_state::fraction1: + { + switch (*p) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + //++precision; + //++decimal_places; + buffer.push_back(*p); + state = numeric_check_state::fraction; + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + case numeric_check_state::fraction: + { + switch (*p) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + //++precision; + //++decimal_places; + buffer.push_back(*p); + break; + case 'e':case 'E': + buffer.push_back(*p); + state = numeric_check_state::exp1; + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + case numeric_check_state::exp1: + { + switch (*p) + { + case '-': + buffer.push_back(*p); + break; + case '+': + break; + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + state = numeric_check_state::exp; + buffer.push_back(*p); + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + case numeric_check_state::exp: + { + switch (*p) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*p); + break; + default: + state = numeric_check_state::not_a_number; + break; + } + break; + } + default: + break; + } + } + + switch (state) + { + case numeric_check_state::null: + more_ = visitor_->null_value(semantic_tag::none, *this, ec); + break; + case numeric_check_state::boolean_true: + more_ = visitor_->bool_value(true, semantic_tag::none, *this, ec); + break; + case numeric_check_state::boolean_false: + more_ = visitor_->bool_value(false, semantic_tag::none, *this, ec); + break; + case numeric_check_state::zero: + case numeric_check_state::integer: + { + if (is_negative) + { + int64_t val{ 0 }; + auto result = jsoncons::detail::decimal_to_integer(buffer_.data(), buffer_.length(), val); + if (result) + { + more_ = visitor_->int64_value(val, semantic_tag::none, *this, ec); + } + else // Must be overflow + { + more_ = visitor_->string_value(buffer_, semantic_tag::bigint, *this, ec); + } + } + else + { + uint64_t val{ 0 }; + auto result = jsoncons::detail::decimal_to_integer(buffer_.data(), buffer_.length(), val); + if (result) + { + more_ = visitor_->uint64_value(val, semantic_tag::none, *this, ec); + } + else if (result.ec == jsoncons::detail::to_integer_errc::overflow) + { + more_ = visitor_->string_value(buffer_, semantic_tag::bigint, *this, ec); + } + else + { + ec = result.ec; + more_ = false; + return; + } + } + break; + } + case numeric_check_state::fraction: + case numeric_check_state::exp: + { + if (options_.lossless_number()) + { + more_ = visitor_->string_value(buffer_,semantic_tag::bigdec, *this, ec); + } + else + { + double d = to_double_(buffer.c_str(), buffer.length()); + more_ = visitor_->double_value(d, semantic_tag::none, *this, ec); + } + break; + } + default: + { + more_ = visitor_->string_value(buffer_, semantic_tag::none, *this, ec); + break; + } + } + } + + void push_state(csv_parse_state state) + { + state_stack_.push_back(state); + } + + csv_parse_state pop_state() + { + JSONCONS_ASSERT(!state_stack_.empty()) + csv_parse_state state = state_stack_.back(); + state_stack_.pop_back(); + return state; + } +}; + +using csv_parser = basic_csv_parser; +using wcsv_parser = basic_csv_parser; + +}} + +#endif + diff --git a/third_party/jsoncons_ext/csv/csv_reader.hpp b/third_party/jsoncons_ext/csv/csv_reader.hpp new file mode 100644 index 0000000000..858f65787f --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv_reader.hpp @@ -0,0 +1,343 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_READER_HPP +#define JSONCONS_CSV_CSV_READER_HPP + +#include +#include +#include +#include // std::allocator +#include // std::move +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace csv { + + template ,typename Allocator=std::allocator> + class basic_csv_reader + { + struct stack_item + { + stack_item() noexcept + : array_begun_(false) + { + } + + bool array_begun_; + }; + using char_type = CharT; + using temp_allocator_type = Allocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + basic_csv_reader(const basic_csv_reader&) = delete; + basic_csv_reader& operator = (const basic_csv_reader&) = delete; + + basic_default_json_visitor default_visitor_; + text_source_adaptor source_; + basic_json_visitor& visitor_; + basic_csv_parser parser_; + + public: + // Structural characters + static constexpr size_t default_max_buffer_size = 16384; + //! Parse an input stream of CSV text into a json object + /*! + \param is The input stream to read from + */ + + template + basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + const Allocator& alloc = Allocator()) + + : basic_csv_reader(std::forward(source), + visitor, + basic_csv_decode_options(), + default_csv_parsing(), + alloc) + { + } + + template + basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + const basic_csv_decode_options& options, + const Allocator& alloc = Allocator()) + + : basic_csv_reader(std::forward(source), + visitor, + options, + default_csv_parsing(), + alloc) + { + } + + template + basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + std::function err_handler, + const Allocator& alloc = Allocator()) + : basic_csv_reader(std::forward(source), + visitor, + basic_csv_decode_options(), + err_handler, + alloc) + { + } + + template + basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + const basic_csv_decode_options& options, + std::function err_handler, + const Allocator& alloc = Allocator()) + : source_(std::forward(source)), + visitor_(visitor), + parser_(options, err_handler, alloc) + + { + } + + ~basic_csv_reader() noexcept = default; + + void read() + { + std::error_code ec; + read(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read(std::error_code& ec) + { + read_internal(ec); + } + + std::size_t line() const + { + return parser_.line(); + } + + std::size_t column() const + { + return parser_.column(); + } + + bool eof() const + { + return parser_.source_exhausted() && source_.eof(); + } + + private: + + void read_internal(std::error_code& ec) + { + if (source_.is_error()) + { + ec = csv_errc::source_error; + return; + } + while (!parser_.stopped()) + { + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + } + } + parser_.parse_some(visitor_, ec); + if (ec) return; + } + } + }; + + template ,typename Allocator=std::allocator> + class legacy_basic_csv_reader + { + struct stack_item + { + stack_item() noexcept + : array_begun_(false) + { + } + + bool array_begun_; + }; + using char_type = CharT; + using temp_allocator_type = Allocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + legacy_basic_csv_reader(const legacy_basic_csv_reader&) = delete; + legacy_basic_csv_reader& operator = (const legacy_basic_csv_reader&) = delete; + + basic_default_json_visitor default_visitor_; + text_source_adaptor source_; + basic_json_visitor& visitor_; + basic_csv_parser parser_; + + public: + // Structural characters + static constexpr size_t default_max_buffer_size = 16384; + //! Parse an input stream of CSV text into a json object + /*! + \param is The input stream to read from + */ + + template + legacy_basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + const Allocator& alloc = Allocator()) + + : legacy_basic_csv_reader(std::forward(source), + visitor, + basic_csv_decode_options(), + default_csv_parsing(), + alloc) + { + } + + template + legacy_basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + const basic_csv_decode_options& options, + const Allocator& alloc = Allocator()) + + : legacy_basic_csv_reader(std::forward(source), + visitor, + options, + default_csv_parsing(), + alloc) + { + } + + template + legacy_basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + std::function err_handler, + const Allocator& alloc = Allocator()) + : legacy_basic_csv_reader(std::forward(source), + visitor, + basic_csv_decode_options(), + err_handler, + alloc) + { + } + + template + legacy_basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + const basic_csv_decode_options& options, + std::function err_handler, + const Allocator& alloc = Allocator(), + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(std::forward(source)), + visitor_(visitor), + parser_(options, err_handler, alloc) + { + } + + template + legacy_basic_csv_reader(Sourceable&& source, + basic_json_visitor& visitor, + const basic_csv_decode_options& options, + std::function err_handler, + const Allocator& alloc = Allocator(), + typename std::enable_if,Sourceable>::value>::type* = 0) + : source_(), + visitor_(visitor), + parser_(options, err_handler, alloc) + { + jsoncons::basic_string_view sv(std::forward(source)); + auto r = unicode_traits::detect_encoding_from_bom(sv.data(), sv.size()); + if (!(r.encoding == unicode_traits::encoding_kind::utf8 || r.encoding == unicode_traits::encoding_kind::undetected)) + { + JSONCONS_THROW(ser_error(json_errc::illegal_unicode_character,parser_.line(),parser_.column())); + } + std::size_t offset = (r.ptr - sv.data()); + parser_.update(sv.data()+offset,sv.size()-offset); + } + + ~legacy_basic_csv_reader() noexcept = default; + + void read() + { + std::error_code ec; + read(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read(std::error_code& ec) + { + read_internal(ec); + } + + std::size_t line() const + { + return parser_.line(); + } + + std::size_t column() const + { + return parser_.column(); + } + + bool eof() const + { + return parser_.source_exhausted() && source_.eof(); + } + + private: + + void read_internal(std::error_code& ec) + { + if (source_.is_error()) + { + ec = csv_errc::source_error; + return; + } + while (!parser_.stopped()) + { + if (parser_.source_exhausted()) + { + auto s = source_.read_buffer(ec); + if (ec) return; + if (s.size() > 0) + { + parser_.update(s.data(),s.size()); + } + } + parser_.parse_some(visitor_, ec); + if (ec) return; + } + } + }; + + using csv_string_reader = basic_csv_reader>; + using wcsv_string_reader = basic_csv_reader>; + using csv_stream_reader = basic_csv_reader>; + using wcsv_stream_reader = basic_csv_reader>; + +}} + +#endif diff --git a/third_party/jsoncons_ext/csv/csv_serializer.hpp b/third_party/jsoncons_ext/csv/csv_serializer.hpp new file mode 100644 index 0000000000..d8028ec64f --- /dev/null +++ b/third_party/jsoncons_ext/csv/csv_serializer.hpp @@ -0,0 +1,12 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_CSV_SERIALIZER_HPP +#define JSONCONS_CSV_CSV_SERIALIZER_HPP + +#include + +#endif diff --git a/third_party/jsoncons_ext/csv/decode_csv.hpp b/third_party/jsoncons_ext/csv/decode_csv.hpp new file mode 100644 index 0000000000..39ce624d2a --- /dev/null +++ b/third_party/jsoncons_ext/csv/decode_csv.hpp @@ -0,0 +1,209 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_DECODE_CSV_HPP +#define JSONCONS_CSV_DECODE_CSV_HPP + +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace csv { + + template + typename std::enable_if::value && + extension_traits::is_sequence_of::value,T>::type + decode_csv(const Source& s, const basic_csv_decode_options& options = basic_csv_decode_options()) + { + using char_type = typename Source::value_type; + + json_decoder decoder; + + basic_csv_reader> reader(s,decoder,options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_char_sequence::value,T>::type + decode_csv(const Source& s, const basic_csv_decode_options& options = basic_csv_decode_options()) + { + using char_type = typename Source::value_type; + + basic_csv_cursor cursor(s, options); + jsoncons::json_decoder> decoder; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_csv(std::basic_istream& is, const basic_csv_decode_options& options = basic_csv_decode_options()) + { + using char_type = CharT; + + json_decoder decoder; + + basic_csv_reader> reader(is,decoder,options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_csv(std::basic_istream& is, const basic_csv_decode_options& options = basic_csv_decode_options()) + { + basic_csv_cursor cursor(is, options); + jsoncons::json_decoder> decoder; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_csv(InputIt first, InputIt last, + const basic_csv_decode_options::value_type>& options = + basic_csv_decode_options::value_type>()) + { + using char_type = typename std::iterator_traits::value_type; + + jsoncons::json_decoder decoder; + basic_csv_reader> reader(iterator_source(first,last), decoder, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_csv(InputIt first, InputIt last, + const basic_csv_decode_options::value_type>& options = + basic_csv_decode_options::value_type>()) + { + using char_type = typename std::iterator_traits::value_type; + + basic_csv_cursor> cursor(iterator_source(first, last), options); + jsoncons::json_decoder> decoder; + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + // With leading allocator_set parameter + + template + typename std::enable_if::value && + extension_traits::is_sequence_of::value,T>::type + decode_csv(const allocator_set& alloc_set, + const Source& s, + const basic_csv_decode_options& options = basic_csv_decode_options()) + { + using char_type = typename Source::value_type; + + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + + basic_csv_reader,TempAllocator> reader(s,decoder,options,alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_char_sequence::value,T>::type + decode_csv(const allocator_set& alloc_set, + const Source& s, + const basic_csv_decode_options& options = basic_csv_decode_options()) + { + using char_type = typename Source::value_type; + + basic_csv_cursor,TempAllocator> cursor(s, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_csv(const allocator_set& alloc_set, + std::basic_istream& is, + const basic_csv_decode_options& options = basic_csv_decode_options()) + { + using char_type = CharT; + + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + + basic_csv_reader,TempAllocator> reader(is,decoder,options,alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_csv(const allocator_set& alloc_set, + std::basic_istream& is, + const basic_csv_decode_options& options = basic_csv_decode_options()) + { + basic_csv_cursor,TempAllocator> cursor(is, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + +} // namespace csv +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/csv/encode_csv.hpp b/third_party/jsoncons_ext/csv/encode_csv.hpp new file mode 100644 index 0000000000..19ba97168f --- /dev/null +++ b/third_party/jsoncons_ext/csv/encode_csv.hpp @@ -0,0 +1,122 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_CSV_ENCODE_CSV_HPP +#define JSONCONS_CSV_ENCODE_CSV_HPP + +#include +#include +#include + +namespace jsoncons { +namespace csv { + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_csv(const T& j, CharContainer& cont, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = typename CharContainer::value_type; + basic_csv_encoder>> encoder(cont,options); + j.dump(encoder); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_csv(const T& val, CharContainer& cont, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = typename CharContainer::value_type; + basic_csv_encoder>> encoder(cont,options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_csv(const T& j, std::basic_ostream& os, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = CharT; + basic_csv_encoder> encoder(os,options); + j.dump(encoder); + } + + template + typename std::enable_if::value,void>::type + encode_csv(const T& val, std::basic_ostream& os, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = CharT; + basic_csv_encoder> encoder(os,options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // with alloc_set.get_temp_allocator()ator_arg_t + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_csv(const allocator_set& alloc_set, + const T& j, CharContainer& cont, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = typename CharContainer::value_type; + basic_csv_encoder>,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + j.dump(encoder); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_char_container::value>::type + encode_csv(const allocator_set& alloc_set, + const T& val, CharContainer& cont, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = typename CharContainer::value_type; + basic_csv_encoder>,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_csv(const allocator_set& alloc_set, + const T& j, std::basic_ostream& os, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = CharT; + basic_csv_encoder,TempAllocator> encoder(os, options, alloc_set.get_temp_allocator()); + j.dump(encoder); + } + + template + typename std::enable_if::value,void>::type + encode_csv(const allocator_set& alloc_set, + const T& val, std::basic_ostream& os, const basic_csv_encode_options& options = basic_csv_encode_options()) + { + using char_type = CharT; + basic_csv_encoder,TempAllocator> encoder(os, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + +} // namespace csv +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jmespath/jmespath.hpp b/third_party/jsoncons_ext/jmespath/jmespath.hpp new file mode 100644 index 0000000000..8c40b99bbd --- /dev/null +++ b/third_party/jsoncons_ext/jmespath/jmespath.hpp @@ -0,0 +1,5213 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JMESPATH_JMESPATH_HPP +#define JSONCONS_JMESPATH_JMESPATH_HPP + +#include +#include +#include // std::unordered_map +#include +#include // std::is_const +#include // std::numeric_limits +#include // std::move +#include // +#include // std::stable_sort, std::reverse +#include // std::abs +#include +#include + +namespace jsoncons { +namespace jmespath { + + enum class operator_kind + { + default_op, // Identifier, CurrentNode, Index, MultiSelectList, MultiSelectHash, FunctionExpression + projection_op, + flatten_projection_op, // FlattenProjection + or_op, + and_op, + eq_op, + ne_op, + lt_op, + lte_op, + gt_op, + gte_op, + not_op + }; + + struct operator_table final + { + static int precedence_level(operator_kind oper) + { + switch (oper) + { + case operator_kind::projection_op: + return 11; + case operator_kind::flatten_projection_op: + return 11; + case operator_kind::or_op: + return 9; + case operator_kind::and_op: + return 8; + case operator_kind::eq_op: + case operator_kind::ne_op: + return 6; + case operator_kind::lt_op: + case operator_kind::lte_op: + case operator_kind::gt_op: + case operator_kind::gte_op: + return 5; + case operator_kind::not_op: + return 1; + default: + return 1; + } + } + + static bool is_right_associative(operator_kind oper) + { + switch (oper) + { + case operator_kind::not_op: + return true; + case operator_kind::projection_op: + return true; + case operator_kind::flatten_projection_op: + return false; + case operator_kind::or_op: + case operator_kind::and_op: + case operator_kind::eq_op: + case operator_kind::ne_op: + case operator_kind::lt_op: + case operator_kind::lte_op: + case operator_kind::gt_op: + case operator_kind::gte_op: + return false; + default: + return false; + } + } + }; + + enum class token_kind + { + current_node, + lparen, + rparen, + begin_multi_select_hash, + end_multi_select_hash, + begin_multi_select_list, + end_multi_select_list, + begin_filter, + end_filter, + pipe, + separator, + key, + literal, + expression, + binary_operator, + unary_operator, + function, + end_function, + argument, + begin_expression_type, + end_expression_type, + end_of_expression + }; + + struct literal_arg_t + { + explicit literal_arg_t() = default; + }; + constexpr literal_arg_t literal_arg{}; + + struct begin_expression_type_arg_t + { + explicit begin_expression_type_arg_t() = default; + }; + constexpr begin_expression_type_arg_t begin_expression_type_arg{}; + + struct end_expression_type_arg_t + { + explicit end_expression_type_arg_t() = default; + }; + constexpr end_expression_type_arg_t end_expression_type_arg{}; + + struct end_of_expression_arg_t + { + explicit end_of_expression_arg_t() = default; + }; + constexpr end_of_expression_arg_t end_of_expression_arg{}; + + struct separator_arg_t + { + explicit separator_arg_t() = default; + }; + constexpr separator_arg_t separator_arg{}; + + struct key_arg_t + { + explicit key_arg_t() = default; + }; + constexpr key_arg_t key_arg{}; + + struct lparen_arg_t + { + explicit lparen_arg_t() = default; + }; + constexpr lparen_arg_t lparen_arg{}; + + struct rparen_arg_t + { + explicit rparen_arg_t() = default; + }; + constexpr rparen_arg_t rparen_arg{}; + + struct begin_multi_select_hash_arg_t + { + explicit begin_multi_select_hash_arg_t() = default; + }; + constexpr begin_multi_select_hash_arg_t begin_multi_select_hash_arg{}; + + struct end_multi_select_hash_arg_t + { + explicit end_multi_select_hash_arg_t() = default; + }; + constexpr end_multi_select_hash_arg_t end_multi_select_hash_arg{}; + + struct begin_multi_select_list_arg_t + { + explicit begin_multi_select_list_arg_t() = default; + }; + constexpr begin_multi_select_list_arg_t begin_multi_select_list_arg{}; + + struct end_multi_select_list_arg_t + { + explicit end_multi_select_list_arg_t() = default; + }; + constexpr end_multi_select_list_arg_t end_multi_select_list_arg{}; + + struct begin_filter_arg_t + { + explicit begin_filter_arg_t() = default; + }; + constexpr begin_filter_arg_t begin_filter_arg{}; + + struct end_filter_arg_t + { + explicit end_filter_arg_t() = default; + }; + constexpr end_filter_arg_t end_filter_arg{}; + + struct pipe_arg_t + { + explicit pipe_arg_t() = default; + }; + constexpr pipe_arg_t pipe_arg{}; + + struct current_node_arg_t + { + explicit current_node_arg_t() = default; + }; + constexpr current_node_arg_t current_node_arg{}; + + struct end_function_arg_t + { + explicit end_function_arg_t() = default; + }; + constexpr end_function_arg_t end_function_arg{}; + + struct argument_arg_t + { + explicit argument_arg_t() = default; + }; + constexpr argument_arg_t argument_arg{}; + + struct slice + { + jsoncons::optional start_; + jsoncons::optional stop_; + int64_t step_; + + slice() + : start_(), stop_(), step_(1) + { + } + + slice(const jsoncons::optional& start, const jsoncons::optional& end, int64_t step) + : start_(start), stop_(end), step_(step) + { + } + + slice(const slice& other) + : start_(other.start_), stop_(other.stop_), step_(other.step_) + { + } + + slice& operator=(const slice& rhs) + { + if (this != &rhs) + { + if (rhs.start_) + { + start_ = rhs.start_; + } + else + { + start_.reset(); + } + if (rhs.stop_) + { + stop_ = rhs.stop_; + } + else + { + stop_.reset(); + } + step_ = rhs.step_; + } + return *this; + } + + int64_t get_start(std::size_t size) const + { + if (start_) + { + auto len = *start_ >= 0 ? *start_ : (static_cast(size) + *start_); + return len <= static_cast(size) ? len : static_cast(size); + } + else + { + if (step_ >= 0) + { + return 0; + } + else + { + return static_cast(size); + } + } + } + + int64_t get_stop(std::size_t size) const + { + if (stop_) + { + auto len = *stop_ >= 0 ? *stop_ : (static_cast(size) + *stop_); + return len <= static_cast(size) ? len : static_cast(size); + } + else + { + return step_ >= 0 ? static_cast(size) : -1; + } + } + + int64_t step() const + { + return step_; // Allow negative + } + }; + + namespace detail { + + enum class path_state + { + start, + lhs_expression, + rhs_expression, + sub_expression, + expression_type, + comparator_expression, + function_expression, + argument, + expression_or_expression_type, + quoted_string, + raw_string, + raw_string_escape_char, + quoted_string_escape_char, + escape_u1, + escape_u2, + escape_u3, + escape_u4, + escape_expect_surrogate_pair1, + escape_expect_surrogate_pair2, + escape_u5, + escape_u6, + escape_u7, + escape_u8, + literal, + key_expr, + val_expr, + identifier_or_function_expr, + unquoted_string, + key_val_expr, + number, + digit, + index_or_slice_expression, + bracket_specifier, + bracket_specifier_or_multi_select_list, + filter, + multi_select_list, + multi_select_hash, + rhs_slice_expression_stop, + rhs_slice_expression_step, + expect_rbracket, + expect_rparen, + expect_dot, + expect_rbrace, + expect_colon, + expect_multi_select_list, + cmp_lt_or_lte, + cmp_eq, + cmp_gt_or_gte, + cmp_ne, + expect_pipe_or_or, + expect_and + }; + + // dynamic_resources + + template + class dynamic_resources + { + typedef typename Json::char_type char_type; + typedef typename Json::char_traits_type char_traits_type; + typedef std::basic_string string_type; + typedef typename Json::string_view_type string_view_type; + typedef JsonReference reference; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + typedef typename Json::const_pointer const_pointer; + + std::vector> temp_storage_; + + public: + ~dynamic_resources() + { + } + + reference number_type_name() + { + static Json number_type_name(JSONCONS_STRING_CONSTANT(char_type, "number")); + + return number_type_name; + } + + reference boolean_type_name() + { + static Json boolean_type_name(JSONCONS_STRING_CONSTANT(char_type, "boolean")); + + return boolean_type_name; + } + + reference string_type_name() + { + static Json string_type_name(JSONCONS_STRING_CONSTANT(char_type, "string")); + + return string_type_name; + } + + reference object_type_name() + { + static Json object_type_name(JSONCONS_STRING_CONSTANT(char_type, "object")); + + return object_type_name; + } + + reference array_type_name() + { + static Json array_type_name(JSONCONS_STRING_CONSTANT(char_type, "array")); + + return array_type_name; + } + + reference null_type_name() + { + static Json null_type_name(JSONCONS_STRING_CONSTANT(char_type, "null")); + + return null_type_name; + } + + reference true_value() const + { + static const Json true_value(true, semantic_tag::none); + return true_value; + } + + reference false_value() const + { + static const Json false_value(false, semantic_tag::none); + return false_value; + } + + reference null_value() const + { + static const Json null_value(null_type(), semantic_tag::none); + return null_value; + } + + template + Json* create_json(Args&& ... args) + { + auto temp = jsoncons::make_unique(std::forward(args)...); + Json* ptr = temp.get(); + temp_storage_.emplace_back(std::move(temp)); + return ptr; + } + }; + + template + class jmespath_evaluator + { + public: + typedef typename Json::char_type char_type; + typedef typename Json::char_traits_type char_traits_type; + typedef std::basic_string string_type; + typedef typename Json::string_view_type string_view_type; + typedef JsonReference reference; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + typedef typename Json::const_pointer const_pointer; + + static bool is_false(reference ref) + { + return (ref.is_array() && ref.empty()) || + (ref.is_object() && ref.empty()) || + (ref.is_string() && ref.as_string_view().size() == 0) || + (ref.is_bool() && !ref.as_bool()) || + ref.is_null(); + } + + static bool is_true(reference ref) + { + return !is_false(ref); + } + + class unary_operator + { + std::size_t precedence_level_; + bool is_right_associative_; + + protected: + ~unary_operator() = default; // virtual destructor not needed + public: + unary_operator(operator_kind oper) + : precedence_level_(operator_table::precedence_level(oper)), + is_right_associative_(operator_table::is_right_associative(oper)) + { + } + + std::size_t precedence_level() const + { + return precedence_level_; + } + bool is_right_associative() const + { + return is_right_associative_; + } + + virtual reference evaluate(reference val, dynamic_resources&, std::error_code& ec) const = 0; + }; + + class not_expression final : public unary_operator + { + public: + not_expression() + : unary_operator(operator_kind::not_op) + {} + + reference evaluate(reference val, dynamic_resources& resources, std::error_code&) const override + { + return is_false(val) ? resources.true_value() : resources.false_value(); + } + }; + + class binary_operator + { + std::size_t precedence_level_; + bool is_right_associative_; + protected: + ~binary_operator() = default; // virtual destructor not needed + public: + binary_operator(operator_kind oper) + : precedence_level_(operator_table::precedence_level(oper)), + is_right_associative_(operator_table::is_right_associative(oper)) + { + } + + + std::size_t precedence_level() const + { + return precedence_level_; + } + bool is_right_associative() const + { + return is_right_associative_; + } + + virtual reference evaluate(reference lhs, reference rhs, dynamic_resources&, std::error_code& ec) const = 0; + + virtual std::string to_string(std::size_t indent = 0) const + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("to_string not implemented\n"); + return s; + } + }; + + // expression_base + class expression_base + { + std::size_t precedence_level_; + bool is_right_associative_; + bool is_projection_; + public: + expression_base(operator_kind oper, bool is_projection) + : precedence_level_(operator_table::precedence_level(oper)), + is_right_associative_(operator_table::is_right_associative(oper)), + is_projection_(is_projection) + { + } + + std::size_t precedence_level() const + { + return precedence_level_; + } + + bool is_right_associative() const + { + return is_right_associative_; + } + + bool is_projection() const + { + return is_projection_; + } + + virtual ~expression_base() = default; + + virtual reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const = 0; + + virtual void add_expression(std::unique_ptr&& expressions) = 0; + + virtual std::string to_string(std::size_t = 0) const + { + return std::string("to_string not implemented"); + } + }; + + // parameter + + enum class parameter_kind{value, expression}; + + class parameter + { + parameter_kind type_; + + union + { + expression_base* expression_; + pointer value_; + }; + + public: + + parameter(const parameter& other) noexcept + : type_(other.type_) + { + switch (type_) + { + case parameter_kind::expression: + expression_ = other.expression_; + break; + case parameter_kind::value: + value_ = other.value_; + break; + default: + break; + } + } + + parameter(reference value) noexcept + : type_(parameter_kind::value), value_(std::addressof(value)) + { + } + + parameter(expression_base* expression) noexcept + : type_(parameter_kind::expression), expression_(expression) + { + } + + parameter& operator=(const parameter& other) + { + if (&other != this) + { + type_ = other.type_; + switch (type_) + { + case parameter_kind::expression: + expression_ = other.expression_; + break; + case parameter_kind::value: + value_ = other.value_; + break; + default: + break; + } + } + return *this; + } + + bool is_value() const + { + return type_ == parameter_kind::value; + } + + bool is_expression() const + { + return type_ == parameter_kind::expression; + } + + const Json& value() const + { + return *value_; + } + + const expression_base& expression() const + { + return *expression_; + } + }; + + // function_base + class function_base + { + jsoncons::optional arg_count_; + public: + function_base(jsoncons::optional arg_count) + : arg_count_(arg_count) + { + } + + jsoncons::optional arity() const + { + return arg_count_; + } + + virtual ~function_base() = default; + + virtual reference evaluate(std::vector& args, dynamic_resources&, std::error_code& ec) const = 0; + + virtual std::string to_string(std::size_t = 0) const + { + return std::string("to_string not implemented"); + } + }; + + class abs_function : public function_base + { + public: + abs_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + switch (arg0.type()) + { + case json_type::uint64_value: + return arg0; + case json_type::int64_value: + { + return arg0.template as() >= 0 ? arg0 : *resources.create_json(std::abs(arg0.template as())); + } + case json_type::double_value: + { + return arg0.template as() >= 0 ? arg0 : *resources.create_json(std::abs(arg0.template as())); + } + default: + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + } + }; + + class avg_function : public function_base + { + public: + avg_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.empty()) + { + return resources.null_value(); + } + + double sum = 0; + for (auto& j : arg0.array_range()) + { + if (!j.is_number()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + sum += j.template as(); + } + + return sum == 0 ? resources.null_value() : *resources.create_json(sum/arg0.size()); + } + }; + + class ceil_function : public function_base + { + public: + ceil_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + switch (arg0.type()) + { + case json_type::uint64_value: + case json_type::int64_value: + { + return *resources.create_json(arg0.template as()); + } + case json_type::double_value: + { + return *resources.create_json(std::ceil(arg0.template as())); + } + default: + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + }; + + class contains_function : public function_base + { + public: + contains_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[1].is_value())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + + reference arg0 = args[0].value(); + reference arg1 = args[1].value(); + + switch (arg0.type()) + { + case json_type::array_value: + for (auto& j : arg0.array_range()) + { + if (j == arg1) + { + return resources.true_value(); + } + } + return resources.false_value(); + case json_type::string_value: + { + if (!arg1.is_string()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + auto sv0 = arg0.template as(); + auto sv1 = arg1.template as(); + return sv0.find(sv1) != string_view_type::npos ? resources.true_value() : resources.false_value(); + } + default: + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + } + }; + + class ends_with_function : public function_base + { + public: + ends_with_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[1].is_value())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_string()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg1 = args[1].value(); + if (!arg1.is_string()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + auto sv0 = arg0.template as(); + auto sv1 = arg1.template as(); + + if (sv1.length() <= sv0.length() && sv1 == sv0.substr(sv0.length() - sv1.length())) + { + return resources.true_value(); + } + else + { + return resources.false_value(); + } + } + }; + + class floor_function : public function_base + { + public: + floor_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + switch (arg0.type()) + { + case json_type::uint64_value: + case json_type::int64_value: + { + return *resources.create_json(arg0.template as()); + } + case json_type::double_value: + { + return *resources.create_json(std::floor(arg0.template as())); + } + default: + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + }; + + class join_function : public function_base + { + public: + join_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + reference arg0 = args[0].value(); + reference arg1 = args[1].value(); + + if (!(args[0].is_value() && args[1].is_value())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + if (!arg0.is_string()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (!arg1.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + string_type sep = arg0.template as(); + string_type buf; + for (auto& j : arg1.array_range()) + { + if (!j.is_string()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (!buf.empty()) + { + buf.append(sep); + } + auto sv = j.template as(); + buf.append(sv.begin(), sv.end()); + } + return *resources.create_json(buf); + } + }; + + class length_function : public function_base + { + public: + length_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + + switch (arg0.type()) + { + case json_type::object_value: + case json_type::array_value: + return *resources.create_json(arg0.size()); + case json_type::string_value: + { + auto sv0 = arg0.template as(); + auto length = unicode_traits::count_codepoints(sv0.data(), sv0.size()); + return *resources.create_json(length); + } + default: + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + } + }; + + class max_function : public function_base + { + public: + max_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.empty()) + { + return resources.null_value(); + } + + bool is_number = arg0.at(0).is_number(); + bool is_string = arg0.at(0).is_string(); + if (!is_number && !is_string) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + std::size_t index = 0; + for (std::size_t i = 1; i < arg0.size(); ++i) + { + if (!(arg0.at(i).is_number() == is_number && arg0.at(i).is_string() == is_string)) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.at(i) > arg0.at(index)) + { + index = i; + } + } + + return arg0.at(index); + } + }; + + class max_by_function : public function_base + { + public: + max_by_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[1].is_expression())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.empty()) + { + return resources.null_value(); + } + + const auto& expr = args[1].expression(); + + std::error_code ec2; + Json key1 = expr.evaluate(arg0.at(0), resources, ec2); + + bool is_number = key1.is_number(); + bool is_string = key1.is_string(); + if (!(is_number || is_string)) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + std::size_t index = 0; + for (std::size_t i = 1; i < arg0.size(); ++i) + { + reference key2 = expr.evaluate(arg0.at(i), resources, ec2); + if (!(key2.is_number() == is_number && key2.is_string() == is_string)) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (key2 > key1) + { + key1 = key2; + index = i; + } + } + + return arg0.at(index); + } + }; + + class map_function : public function_base + { + public: + map_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_expression() && args[1].is_value())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + const auto& expr = args[0].expression(); + + reference arg0 = args[1].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + + for (auto& item : arg0.array_range()) + { + auto& j = expr.evaluate(item, resources, ec); + if (ec) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + result->emplace_back(json_const_pointer_arg, std::addressof(j)); + } + + return *result; + } + + std::string to_string(std::size_t = 0) const override + { + return std::string("map_function\n"); + } + }; + + class min_function : public function_base + { + public: + min_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.empty()) + { + return resources.null_value(); + } + + bool is_number = arg0.at(0).is_number(); + bool is_string = arg0.at(0).is_string(); + if (!is_number && !is_string) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + std::size_t index = 0; + for (std::size_t i = 1; i < arg0.size(); ++i) + { + if (!(arg0.at(i).is_number() == is_number && arg0.at(i).is_string() == is_string)) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.at(i) < arg0.at(index)) + { + index = i; + } + } + + return arg0.at(index); + } + }; + + class min_by_function : public function_base + { + public: + min_by_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[1].is_expression())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.empty()) + { + return resources.null_value(); + } + + const auto& expr = args[1].expression(); + + std::error_code ec2; + Json key1 = expr.evaluate(arg0.at(0), resources, ec2); + + bool is_number = key1.is_number(); + bool is_string = key1.is_string(); + if (!(is_number || is_string)) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + std::size_t index = 0; + for (std::size_t i = 1; i < arg0.size(); ++i) + { + reference key2 = expr.evaluate(arg0.at(i), resources, ec2); + if (!(key2.is_number() == is_number && key2.is_string() == is_string)) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (key2 < key1) + { + key1 = key2; + index = i; + } + } + + return arg0.at(index); + } + }; + + class merge_function : public function_base + { + public: + merge_function() + : function_base(jsoncons::optional()) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + if (args.empty()) + { + ec = jmespath_errc::invalid_arity; + return resources.null_value(); + } + + for (auto& param : args) + { + if (!param.is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + + reference arg0 = args[0].value(); + if (!arg0.is_object()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (args.size() == 1) + { + return arg0; + } + + auto result = resources.create_json(arg0); + for (std::size_t i = 1; i < args.size(); ++i) + { + reference argi = args[i].value(); + if (!argi.is_object()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + for (auto& item : argi.object_range()) + { + result->insert_or_assign(item.key(),item.value()); + } + } + + return *result; + } + }; + + class type_function : public function_base + { + public: + type_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + + switch (arg0.type()) + { + case json_type::int64_value: + case json_type::uint64_value: + case json_type::double_value: + return resources.number_type_name(); + case json_type::bool_value: + return resources.boolean_type_name(); + case json_type::string_value: + return resources.string_type_name(); + case json_type::object_value: + return resources.object_type_name(); + case json_type::array_value: + return resources.array_type_name(); + default: + return resources.null_type_name(); + break; + + } + } + }; + + class sort_function : public function_base + { + public: + sort_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.size() <= 1) + { + return arg0; + } + + bool is_number = arg0.at(0).is_number(); + bool is_string = arg0.at(0).is_string(); + if (!is_number && !is_string) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + for (std::size_t i = 1; i < arg0.size(); ++i) + { + if (arg0.at(i).is_number() != is_number || arg0.at(i).is_string() != is_string) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + + auto v = resources.create_json(arg0); + std::stable_sort((v->array_range()).begin(), (v->array_range()).end()); + return *v; + } + }; + + class sort_by_function : public function_base + { + public: + sort_by_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[1].is_expression())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + if (arg0.size() <= 1) + { + return arg0; + } + + const auto& expr = args[1].expression(); + + auto v = resources.create_json(arg0); + std::stable_sort((v->array_range()).begin(), (v->array_range()).end(), + [&expr,&resources,&ec](reference lhs, reference rhs) -> bool + { + std::error_code ec2; + reference key1 = expr.evaluate(lhs, resources, ec2); + bool is_number = key1.is_number(); + bool is_string = key1.is_string(); + if (!(is_number || is_string)) + { + ec = jmespath_errc::invalid_type; + } + + reference key2 = expr.evaluate(rhs, resources, ec2); + if (!(key2.is_number() == is_number && key2.is_string() == is_string)) + { + ec = jmespath_errc::invalid_type; + } + + return key1 < key2; + }); + return ec ? resources.null_value() : *v; + } + + std::string to_string(std::size_t = 0) const override + { + return std::string("sort_by_function\n"); + } + }; + + class keys_function final : public function_base + { + public: + keys_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_object()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + result->reserve(args.size()); + + for (auto& item : arg0.object_range()) + { + result->emplace_back(item.key()); + } + return *result; + } + }; + + class values_function final : public function_base + { + public: + values_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_object()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + result->reserve(args.size()); + + for (auto& item : arg0.object_range()) + { + result->emplace_back(item.value()); + } + return *result; + } + }; + + class reverse_function final : public function_base + { + public: + reverse_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + switch (arg0.type()) + { + case json_type::string_value: + { + string_view_type sv = arg0.as_string_view(); + std::basic_string buf; + unicode_traits::convert(sv.data(), sv.size(), buf); + std::reverse(buf.begin(), buf.end()); + string_type s; + unicode_traits::convert(buf.data(), buf.size(), s); + return *resources.create_json(s); + } + case json_type::array_value: + { + auto result = resources.create_json(arg0); + std::reverse(result->array_range().begin(),result->array_range().end()); + return *result; + } + default: + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + } + }; + + class starts_with_function : public function_base + { + public: + starts_with_function() + : function_base(2) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!(args[0].is_value() && args[1].is_value())) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_string()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg1 = args[1].value(); + if (!arg1.is_string()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + auto sv0 = arg0.template as(); + auto sv1 = arg1.template as(); + + if (sv1.length() <= sv0.length() && sv1 == sv0.substr(0, sv1.length())) + { + return resources.true_value(); + } + else + { + return resources.false_value(); + } + } + }; + + class sum_function : public function_base + { + public: + sum_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (!arg0.is_array()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + double sum = 0; + for (auto& j : arg0.array_range()) + { + if (!j.is_number()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + sum += j.template as(); + } + + return *resources.create_json(sum); + } + }; + + class to_array_function final : public function_base + { + public: + to_array_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + if (arg0.is_array()) + { + return arg0; + } + else + { + auto result = resources.create_json(json_array_arg); + result->push_back(arg0); + return *result; + } + } + + std::string to_string(std::size_t = 0) const override + { + return std::string("to_array_function\n"); + } + }; + + class to_number_function final : public function_base + { + public: + to_number_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + switch (arg0.type()) + { + case json_type::int64_value: + case json_type::uint64_value: + case json_type::double_value: + return arg0; + case json_type::string_value: + { + auto sv = arg0.as_string_view(); + uint64_t uval{ 0 }; + auto result1 = jsoncons::detail::to_integer(sv.data(), sv.length(), uval); + if (result1) + { + return *resources.create_json(uval); + } + int64_t sval{ 0 }; + auto result2 = jsoncons::detail::to_integer(sv.data(), sv.length(), sval); + if (result2) + { + return *resources.create_json(sval); + } + jsoncons::detail::chars_to to_double; + try + { + auto s = arg0.as_string(); + double d = to_double(s.c_str(), s.length()); + return *resources.create_json(d); + } + catch (const std::exception&) + { + return resources.null_value(); + } + } + default: + return resources.null_value(); + } + } + + std::string to_string(std::size_t = 0) const override + { + return std::string("to_number_function\n"); + } + }; + + class to_string_function final : public function_base + { + public: + to_string_function() + : function_base(1) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code& ec) const override + { + JSONCONS_ASSERT(args.size() == *this->arity()); + + if (!args[0].is_value()) + { + ec = jmespath_errc::invalid_type; + return resources.null_value(); + } + + reference arg0 = args[0].value(); + return *resources.create_json(arg0.template as()); + } + + std::string to_string(std::size_t = 0) const override + { + return std::string("to_string_function\n"); + } + }; + + class not_null_function final : public function_base + { + public: + not_null_function() + : function_base(jsoncons::optional()) + { + } + + reference evaluate(std::vector& args, dynamic_resources& resources, std::error_code&) const override + { + for (auto& param : args) + { + if (param.is_value() && !param.value().is_null()) + { + return param.value(); + } + } + return resources.null_value(); + } + + std::string to_string(std::size_t = 0) const override + { + return std::string("to_string_function\n"); + } + }; + + // token + + class token + { + public: + token_kind type_; + + union + { + std::unique_ptr expression_; + const unary_operator* unary_operator_; + const binary_operator* binary_operator_; + const function_base* function_; + Json value_; + string_type key_; + }; + public: + + token(current_node_arg_t) noexcept + : type_(token_kind::current_node) + { + } + + token(end_function_arg_t) noexcept + : type_(token_kind::end_function) + { + } + + token(separator_arg_t) noexcept + : type_(token_kind::separator) + { + } + + token(lparen_arg_t) noexcept + : type_(token_kind::lparen) + { + } + + token(rparen_arg_t) noexcept + : type_(token_kind::rparen) + { + } + + token(end_of_expression_arg_t) noexcept + : type_(token_kind::end_of_expression) + { + } + + token(begin_multi_select_hash_arg_t) noexcept + : type_(token_kind::begin_multi_select_hash) + { + } + + token(end_multi_select_hash_arg_t) noexcept + : type_(token_kind::end_multi_select_hash) + { + } + + token(begin_multi_select_list_arg_t) noexcept + : type_(token_kind::begin_multi_select_list) + { + } + + token(end_multi_select_list_arg_t) noexcept + : type_(token_kind::end_multi_select_list) + { + } + + token(begin_filter_arg_t) noexcept + : type_(token_kind::begin_filter) + { + } + + token(end_filter_arg_t) noexcept + : type_(token_kind::end_filter) + { + } + + token(pipe_arg_t) noexcept + : type_(token_kind::pipe) + { + } + + token(key_arg_t, const string_type& key) + : type_(token_kind::key) + { + new (&key_) string_type(key); + } + + token(std::unique_ptr&& expression) + : type_(token_kind::expression) + { + new (&expression_) std::unique_ptr(std::move(expression)); + } + + token(const unary_operator* expression) noexcept + : type_(token_kind::unary_operator), + unary_operator_(expression) + { + } + + token(const binary_operator* expression) noexcept + : type_(token_kind::binary_operator), + binary_operator_(expression) + { + } + + token(const function_base* function) noexcept + : type_(token_kind::function), + function_(function) + { + } + + token(argument_arg_t) noexcept + : type_(token_kind::argument) + { + } + + token(begin_expression_type_arg_t) noexcept + : type_(token_kind::begin_expression_type) + { + } + + token(end_expression_type_arg_t) noexcept + : type_(token_kind::end_expression_type) + { + } + + token(literal_arg_t, Json&& value) noexcept + : type_(token_kind::literal), value_(std::move(value)) + { + } + + token(token&& other) noexcept + { + construct(std::move(other)); + } + + token& operator=(token&& other) + { + if (&other != this) + { + if (type_ == other.type_) + { + switch (type_) + { + case token_kind::expression: + expression_ = std::move(other.expression_); + break; + case token_kind::key: + key_ = std::move(other.key_); + break; + case token_kind::unary_operator: + unary_operator_ = other.unary_operator_; + break; + case token_kind::binary_operator: + binary_operator_ = other.binary_operator_; + break; + case token_kind::function: + function_ = other.function_; + break; + case token_kind::literal: + value_ = std::move(other.value_); + break; + default: + break; + } + } + else + { + destroy(); + construct(std::move(other)); + } + } + return *this; + } + + ~token() noexcept + { + destroy(); + } + + token_kind type() const + { + return type_; + } + + bool is_lparen() const + { + return type_ == token_kind::lparen; + } + + bool is_lbrace() const + { + return type_ == token_kind::begin_multi_select_hash; + } + + bool is_key() const + { + return type_ == token_kind::key; + } + + bool is_rparen() const + { + return type_ == token_kind::rparen; + } + + bool is_current_node() const + { + return type_ == token_kind::current_node; + } + + bool is_projection() const + { + return type_ == token_kind::expression && expression_->is_projection(); + } + + bool is_expression() const + { + return type_ == token_kind::expression; + } + + bool is_operator() const + { + return type_ == token_kind::unary_operator || + type_ == token_kind::binary_operator; + } + + std::size_t precedence_level() const + { + switch(type_) + { + case token_kind::unary_operator: + return unary_operator_->precedence_level(); + case token_kind::binary_operator: + return binary_operator_->precedence_level(); + case token_kind::expression: + return expression_->precedence_level(); + default: + return 0; + } + } + + jsoncons::optional arity() const + { + return type_ == token_kind::function ? function_->arity() : jsoncons::optional(); + } + + bool is_right_associative() const + { + switch(type_) + { + case token_kind::unary_operator: + return unary_operator_->is_right_associative(); + case token_kind::binary_operator: + return binary_operator_->is_right_associative(); + case token_kind::expression: + return expression_->is_right_associative(); + default: + return false; + } + } + + void construct(token&& other) + { + type_ = other.type_; + switch (type_) + { + case token_kind::expression: + new (&expression_) std::unique_ptr(std::move(other.expression_)); + break; + case token_kind::key: + new (&key_) string_type(std::move(other.key_)); + break; + case token_kind::unary_operator: + unary_operator_ = other.unary_operator_; + break; + case token_kind::binary_operator: + binary_operator_ = other.binary_operator_; + break; + case token_kind::function: + function_ = other.function_; + break; + case token_kind::literal: + new (&value_) Json(std::move(other.value_)); + break; + default: + break; + } + } + + void destroy() noexcept + { + switch(type_) + { + case token_kind::expression: + expression_.~unique_ptr(); + break; + case token_kind::key: + key_.~basic_string(); + break; + case token_kind::literal: + value_.~Json(); + break; + default: + break; + } + } + + std::string to_string(std::size_t indent = 0) const + { + switch(type_) + { + case token_kind::expression: + return expression_->to_string(indent); + break; + case token_kind::unary_operator: + return std::string("unary_operator"); + break; + case token_kind::binary_operator: + return binary_operator_->to_string(indent); + break; + case token_kind::current_node: + return std::string("current_node"); + break; + case token_kind::end_function: + return std::string("end_function"); + break; + case token_kind::separator: + return std::string("separator"); + break; + case token_kind::literal: + return std::string("literal"); + break; + case token_kind::key: + return std::string("key") + key_; + break; + case token_kind::begin_multi_select_hash: + return std::string("begin_multi_select_hash"); + break; + case token_kind::begin_multi_select_list: + return std::string("begin_multi_select_list"); + break; + case token_kind::begin_filter: + return std::string("begin_filter"); + break; + case token_kind::pipe: + return std::string("pipe"); + break; + case token_kind::lparen: + return std::string("lparen"); + break; + case token_kind::function: + return function_->to_string(); + case token_kind::argument: + return std::string("argument"); + break; + case token_kind::begin_expression_type: + return std::string("begin_expression_type"); + break; + case token_kind::end_expression_type: + return std::string("end_expression_type"); + break; + default: + return std::string("default"); + break; + } + } + }; + + static pointer evaluate_tokens(reference doc, const std::vector& output_stack, dynamic_resources& resources, std::error_code& ec) + { + pointer root_ptr = std::addressof(doc); + std::vector stack; + std::vector arg_stack; + for (std::size_t i = 0; i < output_stack.size(); ++i) + { + auto& t = output_stack[i]; + switch (t.type()) + { + case token_kind::literal: + { + stack.emplace_back(t.value_); + break; + } + case token_kind::begin_expression_type: + { + JSONCONS_ASSERT(i+1 < output_stack.size()); + ++i; + JSONCONS_ASSERT(output_stack[i].is_expression()); + JSONCONS_ASSERT(!stack.empty()); + stack.pop_back(); + stack.emplace_back(output_stack[i].expression_.get()); + break; + } + case token_kind::pipe: + { + JSONCONS_ASSERT(!stack.empty()); + root_ptr = std::addressof(stack.back().value()); + break; + } + case token_kind::current_node: + stack.emplace_back(*root_ptr); + break; + case token_kind::expression: + { + JSONCONS_ASSERT(!stack.empty()); + pointer ptr = std::addressof(stack.back().value()); + stack.pop_back(); + auto& ref = t.expression_->evaluate(*ptr, resources, ec); + stack.emplace_back(ref); + break; + } + case token_kind::unary_operator: + { + JSONCONS_ASSERT(stack.size() >= 1); + pointer ptr = std::addressof(stack.back().value()); + stack.pop_back(); + reference r = t.unary_operator_->evaluate(*ptr, resources, ec); + stack.emplace_back(r); + break; + } + case token_kind::binary_operator: + { + JSONCONS_ASSERT(stack.size() >= 2); + pointer rhs = std::addressof(stack.back().value()); + stack.pop_back(); + pointer lhs = std::addressof(stack.back().value()); + stack.pop_back(); + reference r = t.binary_operator_->evaluate(*lhs,*rhs, resources, ec); + stack.emplace_back(r); + break; + } + case token_kind::argument: + { + JSONCONS_ASSERT(!stack.empty()); + arg_stack.push_back(std::move(stack.back())); + stack.pop_back(); + break; + } + case token_kind::function: + { + if (t.function_->arity() && *(t.function_->arity()) != arg_stack.size()) + { + ec = jmespath_errc::invalid_arity; + return std::addressof(resources.null_value()); + } + + reference r = t.function_->evaluate(arg_stack, resources, ec); + if (ec) + { + return std::addressof(resources.null_value()); + } + arg_stack.clear(); + stack.emplace_back(r); + break; + } + default: + break; + } + } + JSONCONS_ASSERT(stack.size() == 1); + return std::addressof(stack.back().value()); + } + + // Implementations + + class or_operator final : public binary_operator + { + public: + or_operator() + : binary_operator(operator_kind::or_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources& resources, std::error_code&) const override + { + if (lhs.is_null() && rhs.is_null()) + { + return resources.null_value(); + } + if (!is_false(lhs)) + { + return lhs; + } + else + { + return rhs; + } + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("or_operator\n"); + return s; + } + }; + + class and_operator final : public binary_operator + { + public: + and_operator() + : binary_operator(operator_kind::and_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources&, std::error_code&) const override + { + if (is_true(lhs)) + { + return rhs; + } + else + { + return lhs; + } + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("and_operator\n"); + return s; + } + }; + + class eq_operator final : public binary_operator + { + public: + eq_operator() + : binary_operator(operator_kind::eq_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources& resources, std::error_code&) const override + { + return lhs == rhs ? resources.true_value() : resources.false_value(); + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("eq_operator\n"); + return s; + } + }; + + class ne_operator final : public binary_operator + { + public: + ne_operator() + : binary_operator(operator_kind::ne_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources& resources, std::error_code&) const override + { + return lhs != rhs ? resources.true_value() : resources.false_value(); + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("ne_operator\n"); + return s; + } + }; + + class lt_operator final : public binary_operator + { + public: + lt_operator() + : binary_operator(operator_kind::lt_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources& resources, std::error_code&) const override + { + if (!(lhs.is_number() && rhs.is_number())) + { + return resources.null_value(); + } + return lhs < rhs ? resources.true_value() : resources.false_value(); + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("lt_operator\n"); + return s; + } + }; + + class lte_operator final : public binary_operator + { + public: + lte_operator() + : binary_operator(operator_kind::lte_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources& resources, std::error_code&) const override + { + if (!(lhs.is_number() && rhs.is_number())) + { + return resources.null_value(); + } + return lhs <= rhs ? resources.true_value() : resources.false_value(); + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("lte_operator\n"); + return s; + } + }; + + class gt_operator final : public binary_operator + { + public: + gt_operator() + : binary_operator(operator_kind::gt_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources& resources, std::error_code&) const override + { + if (!(lhs.is_number() && rhs.is_number())) + { + return resources.null_value(); + } + return lhs > rhs ? resources.true_value() : resources.false_value(); + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("gt_operator\n"); + return s; + } + }; + + class gte_operator final : public binary_operator + { + public: + gte_operator() + : binary_operator(operator_kind::gte_op) + { + } + + reference evaluate(reference lhs, reference rhs, dynamic_resources& resources, std::error_code&) const override + { + if (!(lhs.is_number() && rhs.is_number())) + { + return resources.null_value(); + } + return lhs >= rhs ? resources.true_value() : resources.false_value(); + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("gte_operator\n"); + return s; + } + }; + + // basic_expression + class basic_expression : public expression_base + { + public: + basic_expression() + : expression_base(operator_kind::default_op, false) + { + } + + void add_expression(std::unique_ptr&&) override + { + } + }; + + class identifier_selector final : public basic_expression + { + private: + string_type identifier_; + public: + identifier_selector(const string_view_type& name) + : identifier_(name) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code&) const override + { + //std::cout << "(identifier_selector " << identifier_ << " ) " << pretty_print(val) << "\n"; + if (val.is_object() && val.contains(identifier_)) + { + return val.at(identifier_); + } + else + { + return resources.null_value(); + } + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("identifier_selector "); + s.append(identifier_); + return s; + } + }; + + class current_node final : public basic_expression + { + public: + current_node() + { + } + + reference evaluate(reference val, dynamic_resources&, std::error_code&) const override + { + return val; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("current_node "); + return s; + } + }; + + class index_selector final : public basic_expression + { + int64_t index_; + public: + index_selector(int64_t index) + : index_(index) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code&) const override + { + if (!val.is_array()) + { + return resources.null_value(); + } + int64_t slen = static_cast(val.size()); + if (index_ >= 0 && index_ < slen) + { + std::size_t index = static_cast(index_); + return val.at(index); + } + else if ((slen + index_) >= 0 && (slen+index_) < slen) + { + std::size_t index = static_cast(slen + index_); + return val.at(index); + } + else + { + return resources.null_value(); + } + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("index_selector "); + s.append(std::to_string(index_)); + return s; + } + }; + + // projection_base + class projection_base : public expression_base + { + protected: + std::vector> expressions_; + public: + projection_base(operator_kind oper) + : expression_base(oper, true) + { + } + + void add_expression(std::unique_ptr&& expr) override + { + if (!expressions_.empty() && expressions_.back()->is_projection() && + (expr->precedence_level() < expressions_.back()->precedence_level() || + (expr->precedence_level() == expressions_.back()->precedence_level() && expr->is_right_associative()))) + { + expressions_.back()->add_expression(std::move(expr)); + } + else + { + expressions_.emplace_back(std::move(expr)); + } + } + + reference apply_expressions(reference val, dynamic_resources& resources, std::error_code& ec) const + { + pointer ptr = std::addressof(val); + for (auto& expression : expressions_) + { + ptr = std::addressof(expression->evaluate(*ptr, resources, ec)); + } + return *ptr; + } + }; + + class object_projection final : public projection_base + { + public: + object_projection() + : projection_base(operator_kind::projection_op) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + if (!val.is_object()) + { + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + for (auto& item : val.object_range()) + { + if (!item.value().is_null()) + { + reference j = this->apply_expressions(item.value(), resources, ec); + if (!j.is_null()) + { + result->emplace_back(json_const_pointer_arg, std::addressof(j)); + } + } + } + return *result; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("object_projection\n"); + for (auto& expr : this->expressions_) + { + std::string sss = expr->to_string(indent+2); + s.insert(s.end(), sss.begin(), sss.end()); + s.push_back('\n'); + } + return s; + } + }; + + class list_projection final : public projection_base + { + public: + list_projection() + : projection_base(operator_kind::projection_op) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + if (!val.is_array()) + { + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + for (reference item : val.array_range()) + { + if (!item.is_null()) + { + reference j = this->apply_expressions(item, resources, ec); + if (!j.is_null()) + { + result->emplace_back(json_const_pointer_arg, std::addressof(j)); + } + } + } + return *result; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("list_projection\n"); + for (auto& expr : this->expressions_) + { + std::string sss = expr->to_string(indent+2); + s.insert(s.end(), sss.begin(), sss.end()); + s.push_back('\n'); + } + return s; + } + }; + + class slice_projection final : public projection_base + { + slice slice_; + public: + slice_projection(const slice& s) + : projection_base(operator_kind::projection_op), slice_(s) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + if (!val.is_array()) + { + return resources.null_value(); + } + + auto start = slice_.get_start(val.size()); + auto end = slice_.get_stop(val.size()); + auto step = slice_.step(); + + if (step == 0) + { + ec = jmespath_errc::step_cannot_be_zero; + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + if (step > 0) + { + if (start < 0) + { + start = 0; + } + if (end > static_cast(val.size())) + { + end = val.size(); + } + for (int64_t i = start; i < end; i += step) + { + reference j = this->apply_expressions(val.at(static_cast(i)), resources, ec); + if (!j.is_null()) + { + result->emplace_back(json_const_pointer_arg, std::addressof(j)); + } + } + } + else + { + if (start >= static_cast(val.size())) + { + start = static_cast(val.size()) - 1; + } + if (end < -1) + { + end = -1; + } + for (int64_t i = start; i > end; i += step) + { + reference j = this->apply_expressions(val.at(static_cast(i)), resources, ec); + if (!j.is_null()) + { + result->emplace_back(json_const_pointer_arg, std::addressof(j)); + } + } + } + + return *result; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("slice_projection\n"); + for (auto& expr : this->expressions_) + { + std::string sss = expr->to_string(indent+2); + s.insert(s.end(), sss.begin(), sss.end()); + s.push_back('\n'); + } + return s; + } + }; + + class filter_expression final : public projection_base + { + std::vector token_list_; + public: + filter_expression(std::vector&& token_list) + : projection_base(operator_kind::projection_op), token_list_(std::move(token_list)) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + if (!val.is_array()) + { + return resources.null_value(); + } + auto result = resources.create_json(json_array_arg); + + for (auto& item : val.array_range()) + { + Json j(json_const_pointer_arg, evaluate_tokens(item, token_list_, resources, ec)); + if (is_true(j)) + { + reference jj = this->apply_expressions(item, resources, ec); + if (!jj.is_null()) + { + result->emplace_back(json_const_pointer_arg, std::addressof(jj)); + } + } + } + return *result; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("filter_expression\n"); + for (auto& item : token_list_) + { + std::string sss = item.to_string(indent+2); + s.insert(s.end(), sss.begin(), sss.end()); + s.push_back('\n'); + } + return s; + } + }; + + class flatten_projection final : public projection_base + { + public: + flatten_projection() + : projection_base(operator_kind::flatten_projection_op) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + if (!val.is_array()) + { + return resources.null_value(); + } + + auto result = resources.create_json(json_array_arg); + for (reference current_elem : val.array_range()) + { + if (current_elem.is_array()) + { + for (reference elem : current_elem.array_range()) + { + if (!elem.is_null()) + { + reference j = this->apply_expressions(elem, resources, ec); + if (!j.is_null()) + { + result->emplace_back(json_const_pointer_arg, std::addressof(j)); + } + } + } + } + else + { + if (!current_elem.is_null()) + { + reference j = this->apply_expressions(current_elem, resources, ec); + if (!j.is_null()) + { + result->emplace_back(json_const_pointer_arg, std::addressof(j)); + } + } + } + } + return *result; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("flatten_projection\n"); + for (auto& expr : this->expressions_) + { + std::string sss = expr->to_string(indent+2); + s.insert(s.end(), sss.begin(), sss.end()); + s.push_back('\n'); + } + return s; + } + }; + + class multi_select_list final : public basic_expression + { + std::vector> token_lists_; + public: + multi_select_list(std::vector>&& token_lists) + : token_lists_(std::move(token_lists)) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + if (val.is_null()) + { + return val; + } + auto result = resources.create_json(json_array_arg); + result->reserve(token_lists_.size()); + + for (auto& list : token_lists_) + { + result->emplace_back(json_const_pointer_arg, evaluate_tokens(val, list, resources, ec)); + } + return *result; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("multi_select_list\n"); + for (auto& list : token_lists_) + { + for (auto& item : list) + { + std::string sss = item.to_string(indent+2); + s.insert(s.end(), sss.begin(), sss.end()); + s.push_back('\n'); + } + s.append("---\n"); + } + return s; + } + }; + + struct key_tokens + { + string_type key; + std::vector tokens; + + key_tokens(string_type&& Key, std::vector&& Tokens) noexcept + : key(std::move(Key)), tokens(std::move(Tokens)) + { + } + }; + + class multi_select_hash final : public basic_expression + { + public: + std::vector key_toks_; + + multi_select_hash(std::vector&& key_toks) + : key_toks_(std::move(key_toks)) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + if (val.is_null()) + { + return val; + } + auto resultp = resources.create_json(json_object_arg); + resultp->reserve(key_toks_.size()); + for (auto& item : key_toks_) + { + resultp->try_emplace(item.key, json_const_pointer_arg, evaluate_tokens(val, item.tokens, resources, ec)); + } + + return *resultp; + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("multi_select_list\n"); + return s; + } + }; + + class function_expression final : public basic_expression + { + public: + std::vector toks_; + + function_expression(std::vector&& toks) + : toks_(std::move(toks)) + { + } + + reference evaluate(reference val, dynamic_resources& resources, std::error_code& ec) const override + { + return *evaluate_tokens(val, toks_, resources, ec); + } + + std::string to_string(std::size_t indent = 0) const override + { + std::string s; + for (std::size_t i = 0; i <= indent; ++i) + { + s.push_back(' '); + } + s.append("function_expression\n"); + for (auto& tok : toks_) + { + for (std::size_t i = 0; i <= indent+2; ++i) + { + s.push_back(' '); + } + std::string sss = tok.to_string(indent+2); + s.insert(s.end(), sss.begin(), sss.end()); + s.push_back('\n'); + } + return s; + } + }; + + class static_resources + { + std::vector> temp_storage_; + + public: + + static_resources() = default; + static_resources(const static_resources& expr) = delete; + static_resources& operator=(const static_resources& expr) = delete; + static_resources(static_resources&& expr) = default; + static_resources& operator=(static_resources&& expr) = default; + + const function_base* get_function(const string_type& name, std::error_code& ec) const + { + static abs_function abs_func; + static avg_function avg_func; + static ceil_function ceil_func; + static contains_function contains_func; + static ends_with_function ends_with_func; + static floor_function floor_func; + static join_function join_func; + static length_function length_func; + static max_function max_func; + static max_by_function max_by_func; + static map_function map_func; + static merge_function merge_func; + static min_function min_func; + static min_by_function min_by_func; + static type_function type_func; + static sort_function sort_func; + static sort_by_function sort_by_func; + static keys_function keys_func; + static values_function values_func; + static reverse_function reverse_func; + static starts_with_function starts_with_func; + static const sum_function sum_func; + static to_array_function to_array_func; + static to_number_function to_number_func; + static to_string_function to_string_func; + static not_null_function not_null_func; + + using function_dictionary = std::unordered_map; + static const function_dictionary functions_ = + { + {string_type{'a','b','s'}, &abs_func}, + {string_type{'a','v','g'}, &avg_func}, + {string_type{'c','e','i', 'l'}, &ceil_func}, + {string_type{'c','o','n', 't', 'a', 'i', 'n', 's'}, &contains_func}, + {string_type{'e','n','d', 's', '_', 'w', 'i', 't', 'h'}, &ends_with_func}, + {string_type{'f','l','o', 'o', 'r'}, &floor_func}, + {string_type{'j','o','i', 'n'}, &join_func}, + {string_type{'l','e','n', 'g', 't', 'h'}, &length_func}, + {string_type{'m','a','x'}, &max_func}, + {string_type{'m','a','x','_','b','y'}, &max_by_func}, + {string_type{'m','a','p'}, &map_func}, + {string_type{'m','i','n'}, &min_func}, + {string_type{'m','i','n','_','b','y'}, &min_by_func}, + {string_type{'m','e','r', 'g', 'e'}, &merge_func}, + {string_type{'t','y','p', 'e'}, &type_func}, + {string_type{'s','o','r', 't'}, &sort_func}, + {string_type{'s','o','r', 't','_','b','y'}, &sort_by_func}, + {string_type{'k','e','y', 's'}, &keys_func}, + {string_type{'v','a','l', 'u','e','s'}, &values_func}, + {string_type{'r','e','v', 'e', 'r', 's','e'}, &reverse_func}, + {string_type{'s','t','a', 'r','t','s','_','w','i','t','h'}, &starts_with_func}, + {string_type{'s','u','m'}, &sum_func}, + {string_type{'t','o','_','a','r','r','a','y',}, &to_array_func}, + {string_type{'t','o','_', 'n', 'u', 'm','b','e','r'}, &to_number_func}, + {string_type{'t','o','_', 's', 't', 'r','i','n','g'}, &to_string_func}, + {string_type{'n','o','t', '_', 'n', 'u','l','l'}, ¬_null_func} + }; + auto it = functions_.find(name); + if (it == functions_.end()) + { + ec = jmespath_errc::unknown_function; + return nullptr; + } + return it->second; + } + + const unary_operator* get_not_operator() const + { + static const not_expression not_oper; + + return ¬_oper; + } + + const binary_operator* get_or_operator() const + { + static const or_operator or_oper; + + return &or_oper; + } + + const binary_operator* get_and_operator() const + { + static const and_operator and_oper; + + return &and_oper; + } + + const binary_operator* get_eq_operator() const + { + static const eq_operator eq_oper; + return &eq_oper; + } + + const binary_operator* get_ne_operator() const + { + static const ne_operator ne_oper; + return &ne_oper; + } + + const binary_operator* get_lt_operator() const + { + static const lt_operator lt_oper; + return <_oper; + } + + const binary_operator* get_lte_operator() const + { + static const lte_operator lte_oper; + return <e_oper; + } + + const binary_operator* get_gt_operator() const + { + static const gt_operator gt_oper; + return >_oper; + } + + const binary_operator* get_gte_operator() const + { + static const gte_operator gte_oper; + return >e_oper; + } + }; + + class jmespath_expression + { + static_resources resources_; + std::vector output_stack_; + public: + jmespath_expression() + { + } + + jmespath_expression(const jmespath_expression& expr) = delete; + jmespath_expression& operator=(const jmespath_expression& expr) = delete; + + jmespath_expression(jmespath_expression&& expr) + : resources_(std::move(expr.resources_)), + output_stack_(std::move(expr.output_stack_)) + { + } + + jmespath_expression(static_resources&& resources, + std::vector&& output_stack) + : resources_(std::move(resources)), output_stack_(std::move(output_stack)) + { + } + + Json evaluate(reference doc) const + { + if (output_stack_.empty()) + { + return Json::null(); + } + std::error_code ec; + Json result = evaluate(doc, ec); + if (ec) + { + JSONCONS_THROW(jmespath_error(ec)); + } + return result; + } + + Json evaluate(reference doc, std::error_code& ec) const + { + if (output_stack_.empty()) + { + return Json::null(); + } + dynamic_resources dynamic_storage; + return deep_copy(*evaluate_tokens(doc, output_stack_, dynamic_storage, ec)); + } + + static jmespath_expression compile(const string_view_type& expr) + { + jsoncons::jmespath::detail::jmespath_evaluator evaluator; + std::error_code ec; + jmespath_expression result = evaluator.compile(expr.data(), expr.size(), ec); + if (ec) + { + JSONCONS_THROW(jmespath_error(ec, evaluator.line(), evaluator.column())); + } + return result; + } + + static jmespath_expression compile(const string_view_type& expr, + std::error_code& ec) + { + jsoncons::jmespath::detail::jmespath_evaluator evaluator; + return evaluator.compile(expr.data(), expr.size(), ec); + } + }; + private: + std::size_t line_; + std::size_t column_; + const char_type* begin_input_; + const char_type* end_input_; + const char_type* p_; + + static_resources resources_; + std::vector state_stack_; + + std::vector output_stack_; + std::vector operator_stack_; + + public: + jmespath_evaluator() + : line_(1), column_(1), + begin_input_(nullptr), end_input_(nullptr), + p_(nullptr) + { + } + + std::size_t line() const + { + return line_; + } + + std::size_t column() const + { + return column_; + } + + jmespath_expression compile(const char_type* path, + std::size_t length, + std::error_code& ec) + { + push_token(current_node_arg, ec); + if (ec) {return jmespath_expression();} + state_stack_.emplace_back(path_state::start); + + string_type buffer; + uint32_t cp = 0; + uint32_t cp2 = 0; + + begin_input_ = path; + end_input_ = path + length; + p_ = begin_input_; + + slice slic{}; + + while (p_ < end_input_) + { + switch (state_stack_.back()) + { + case path_state::start: + { + state_stack_.back() = path_state::rhs_expression; + state_stack_.emplace_back(path_state::lhs_expression); + break; + } + case path_state::rhs_expression: + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '.': + ++p_; + ++column_; + state_stack_.emplace_back(path_state::sub_expression); + break; + case '|': + ++p_; + ++column_; + state_stack_.emplace_back(path_state::lhs_expression); + state_stack_.emplace_back(path_state::expect_pipe_or_or); + break; + case '&': + ++p_; + ++column_; + state_stack_.emplace_back(path_state::lhs_expression); + state_stack_.emplace_back(path_state::expect_and); + break; + case '<': + case '>': + case '=': + { + state_stack_.emplace_back(path_state::comparator_expression); + break; + } + case '!': + { + ++p_; + ++column_; + state_stack_.emplace_back(path_state::lhs_expression); + state_stack_.emplace_back(path_state::cmp_ne); + break; + } + case ')': + { + state_stack_.pop_back(); + break; + } + case '[': + state_stack_.emplace_back(path_state::bracket_specifier); + ++p_; + ++column_; + break; + default: + if (state_stack_.size() > 1) + { + state_stack_.pop_back(); + } + else + { + ec = jmespath_errc::syntax_error; + return jmespath_expression(); + } + break; + } + break; + case path_state::comparator_expression: + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '<': + ++p_; + ++column_; + state_stack_.back() = path_state::lhs_expression; + state_stack_.emplace_back(path_state::cmp_lt_or_lte); + break; + case '>': + ++p_; + ++column_; + state_stack_.back() = path_state::lhs_expression; + state_stack_.emplace_back(path_state::cmp_gt_or_gte); + break; + case '=': + { + ++p_; + ++column_; + state_stack_.back() = path_state::lhs_expression; + state_stack_.emplace_back(path_state::cmp_eq); + break; + } + default: + if (state_stack_.size() > 1) + { + state_stack_.pop_back(); + } + else + { + ec = jmespath_errc::syntax_error; + return jmespath_expression(); + } + break; + } + break; + case path_state::lhs_expression: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '\"': + state_stack_.back() = path_state::val_expr; + state_stack_.emplace_back(path_state::quoted_string); + ++p_; + ++column_; + break; + case '\'': + state_stack_.back() = path_state::raw_string; + ++p_; + ++column_; + break; + case '`': + state_stack_.back() = path_state::literal; + ++p_; + ++column_; + break; + case '{': + push_token(begin_multi_select_hash_arg, ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::multi_select_hash; + ++p_; + ++column_; + break; + case '*': // wildcard + push_token(token(jsoncons::make_unique()), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case '(': + { + ++p_; + ++column_; + push_token(lparen_arg, ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::expect_rparen; + state_stack_.emplace_back(path_state::rhs_expression); + state_stack_.emplace_back(path_state::lhs_expression); + break; + } + case '!': + { + ++p_; + ++column_; + push_token(token(resources_.get_not_operator()), ec); + if (ec) {return jmespath_expression();} + break; + } + case '@': + ++p_; + ++column_; + push_token(token(jsoncons::make_unique()), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + case '[': + state_stack_.back() = path_state::bracket_specifier_or_multi_select_list; + ++p_; + ++column_; + break; + default: + if ((*p_ >= 'A' && *p_ <= 'Z') || (*p_ >= 'a' && *p_ <= 'z') || (*p_ == '_')) + { + state_stack_.back() = path_state::identifier_or_function_expr; + state_stack_.emplace_back(path_state::unquoted_string); + buffer.push_back(*p_); + ++p_; + ++column_; + } + else + { + ec = jmespath_errc::expected_identifier; + return jmespath_expression(); + } + break; + }; + break; + } + case path_state::sub_expression: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '\"': + state_stack_.back() = path_state::val_expr; + state_stack_.emplace_back(path_state::quoted_string); + ++p_; + ++column_; + break; + case '{': + push_token(begin_multi_select_hash_arg, ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::multi_select_hash; + ++p_; + ++column_; + break; + case '*': + push_token(token(jsoncons::make_unique()), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case '[': + state_stack_.back() = path_state::expect_multi_select_list; + ++p_; + ++column_; + break; + default: + if ((*p_ >= 'A' && *p_ <= 'Z') || (*p_ >= 'a' && *p_ <= 'z') || (*p_ == '_')) + { + state_stack_.back() = path_state::identifier_or_function_expr; + state_stack_.emplace_back(path_state::unquoted_string); + buffer.push_back(*p_); + ++p_; + ++column_; + } + else + { + ec = jmespath_errc::expected_identifier; + return jmespath_expression(); + } + break; + }; + break; + } + case path_state::key_expr: + push_token(token(key_arg, buffer), ec); + if (ec) {return jmespath_expression();} + buffer.clear(); + state_stack_.pop_back(); + break; + case path_state::val_expr: + push_token(token(jsoncons::make_unique(buffer)), ec); + if (ec) {return jmespath_expression();} + buffer.clear(); + state_stack_.pop_back(); + break; + case path_state::expression_or_expression_type: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '&': + push_token(token(begin_expression_type_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::expression_type; + state_stack_.emplace_back(path_state::rhs_expression); + state_stack_.emplace_back(path_state::lhs_expression); + ++p_; + ++column_; + break; + default: + state_stack_.back() = path_state::argument; + state_stack_.emplace_back(path_state::rhs_expression); + state_stack_.emplace_back(path_state::lhs_expression); + break; + } + break; + case path_state::identifier_or_function_expr: + switch(*p_) + { + case '(': + { + auto f = resources_.get_function(buffer, ec); + if (ec) + { + return jmespath_expression(); + } + buffer.clear(); + push_token(token(f), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::function_expression; + state_stack_.emplace_back(path_state::expression_or_expression_type); + ++p_; + ++column_; + break; + } + default: + { + push_token(token(jsoncons::make_unique(buffer)), ec); + if (ec) {return jmespath_expression();} + buffer.clear(); + state_stack_.pop_back(); + break; + } + } + break; + + case path_state::function_expression: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.emplace_back(path_state::expression_or_expression_type); + ++p_; + ++column_; + break; + case ')': + { + push_token(token(end_function_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + } + default: + break; + } + break; + + case path_state::argument: + push_token(argument_arg, ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + + case path_state::expression_type: + push_token(end_expression_type_arg, ec); + push_token(argument_arg, ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + + case path_state::quoted_string: + switch (*p_) + { + case '\"': + state_stack_.pop_back(); // quoted_string + ++p_; + ++column_; + break; + case '\\': + state_stack_.emplace_back(path_state::quoted_string_escape_char); + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + + case path_state::unquoted_string: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + state_stack_.pop_back(); // unquoted_string + advance_past_space_character(); + break; + default: + if ((*p_ >= '0' && *p_ <= '9') || (*p_ >= 'A' && *p_ <= 'Z') || (*p_ >= 'a' && *p_ <= 'z') || (*p_ == '_')) + { + buffer.push_back(*p_); + ++p_; + ++column_; + } + else + { + state_stack_.pop_back(); // unquoted_string + } + break; + }; + break; + case path_state::raw_string_escape_char: + switch (*p_) + { + case '\'': + buffer.push_back(*p_); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + buffer.push_back('\\'); + buffer.push_back(*p_); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + } + break; + case path_state::quoted_string_escape_char: + switch (*p_) + { + case '\"': + buffer.push_back('\"'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case '\\': + buffer.push_back('\\'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case '/': + buffer.push_back('/'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'b': + buffer.push_back('\b'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'f': + buffer.push_back('\f'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'n': + buffer.push_back('\n'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'r': + buffer.push_back('\r'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 't': + buffer.push_back('\t'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'u': + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u1; + break; + default: + ec = jmespath_errc::illegal_escaped_character; + return jmespath_expression(); + } + break; + case path_state::escape_u1: + cp = append_to_codepoint(0, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u2; + break; + case path_state::escape_u2: + cp = append_to_codepoint(cp, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u3; + break; + case path_state::escape_u3: + cp = append_to_codepoint(cp, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u4; + break; + case path_state::escape_u4: + cp = append_to_codepoint(cp, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + if (unicode_traits::is_high_surrogate(cp)) + { + ++p_; + ++column_; + state_stack_.back() = path_state::escape_expect_surrogate_pair1; + } + else + { + unicode_traits::convert(&cp, 1, buffer); + ++p_; + ++column_; + state_stack_.pop_back(); + } + break; + case path_state::escape_expect_surrogate_pair1: + switch (*p_) + { + case '\\': + ++p_; + ++column_; + state_stack_.back() = path_state::escape_expect_surrogate_pair2; + break; + default: + ec = jmespath_errc::invalid_codepoint; + return jmespath_expression(); + } + break; + case path_state::escape_expect_surrogate_pair2: + switch (*p_) + { + case 'u': + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u5; + break; + default: + ec = jmespath_errc::invalid_codepoint; + return jmespath_expression(); + } + break; + case path_state::escape_u5: + cp2 = append_to_codepoint(0, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u6; + break; + case path_state::escape_u6: + cp2 = append_to_codepoint(cp2, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u7; + break; + case path_state::escape_u7: + cp2 = append_to_codepoint(cp2, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u8; + break; + case path_state::escape_u8: + { + cp2 = append_to_codepoint(cp2, *p_, ec); + if (ec) + { + return jmespath_expression(); + } + uint32_t codepoint = 0x10000 + ((cp & 0x3FF) << 10) + (cp2 & 0x3FF); + unicode_traits::convert(&codepoint, 1, buffer); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + } + case path_state::raw_string: + switch (*p_) + { + case '\'': + { + push_token(token(literal_arg, Json(buffer)), ec); + if (ec) {return jmespath_expression();} + buffer.clear(); + state_stack_.pop_back(); // raw_string + ++p_; + ++column_; + break; + } + case '\\': + state_stack_.emplace_back(path_state::raw_string_escape_char); + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case path_state::literal: + switch (*p_) + { + case '`': + { + json_decoder decoder; + basic_json_reader> reader(buffer, decoder); + std::error_code parse_ec; + reader.read(parse_ec); + if (parse_ec) + { + ec = jmespath_errc::invalid_literal; + return jmespath_expression(); + } + auto j = decoder.get_result(); + + push_token(token(literal_arg, std::move(j)), ec); + if (ec) {return jmespath_expression();} + buffer.clear(); + state_stack_.pop_back(); // json_value + ++p_; + ++column_; + break; + } + case '\\': + if (p_+1 < end_input_) + { + ++p_; + ++column_; + if (*p_ != '`') + { + buffer.push_back('\\'); + } + buffer.push_back(*p_); + } + else + { + ec = jmespath_errc::unexpected_end_of_input; + return jmespath_expression(); + } + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case path_state::number: + switch(*p_) + { + case '-': + buffer.push_back(*p_); + state_stack_.back() = path_state::digit; + ++p_; + ++column_; + break; + default: + state_stack_.back() = path_state::digit; + break; + } + break; + case path_state::digit: + switch(*p_) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*p_); + ++p_; + ++column_; + break; + default: + state_stack_.pop_back(); // digit + break; + } + break; + + case path_state::bracket_specifier: + switch(*p_) + { + case '*': + push_token(token(jsoncons::make_unique()), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::expect_rbracket; + ++p_; + ++column_; + break; + case ']': // [] + push_token(token(jsoncons::make_unique()), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); // bracket_specifier + ++p_; + ++column_; + break; + case '?': + push_token(token(begin_filter_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::filter; + state_stack_.emplace_back(path_state::rhs_expression); + state_stack_.emplace_back(path_state::lhs_expression); + ++p_; + ++column_; + break; + case ':': // slice_expression + state_stack_.back() = path_state::rhs_slice_expression_stop ; + state_stack_.emplace_back(path_state::number); + ++p_; + ++column_; + break; + // number + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + state_stack_.back() = path_state::index_or_slice_expression; + state_stack_.emplace_back(path_state::number); + break; + default: + ec = jmespath_errc::expected_index_expression; + return jmespath_expression(); + } + break; + case path_state::bracket_specifier_or_multi_select_list: + switch(*p_) + { + case '*': + if (p_+1 >= end_input_) + { + ec = jmespath_errc::unexpected_end_of_input; + return jmespath_expression(); + } + if (*(p_+1) == ']') + { + state_stack_.back() = path_state::bracket_specifier; + } + else + { + push_token(token(begin_multi_select_list_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::multi_select_list; + state_stack_.emplace_back(path_state::lhs_expression); + } + break; + case ']': // [] + case '?': + case ':': // slice_expression + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + state_stack_.back() = path_state::bracket_specifier; + break; + default: + push_token(token(begin_multi_select_list_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::multi_select_list; + state_stack_.emplace_back(path_state::lhs_expression); + break; + } + break; + + case path_state::expect_multi_select_list: + switch(*p_) + { + case ']': + case '?': + case ':': + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + ec = jmespath_errc::expected_multi_select_list; + return jmespath_expression(); + case '*': + push_token(token(jsoncons::make_unique()), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::expect_rbracket; + ++p_; + ++column_; + break; + default: + push_token(token(begin_multi_select_list_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::multi_select_list; + state_stack_.emplace_back(path_state::lhs_expression); + break; + } + break; + + case path_state::multi_select_hash: + switch(*p_) + { + case '*': + case ']': + case '?': + case ':': + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + break; + default: + state_stack_.back() = path_state::key_val_expr; + break; + } + break; + + case path_state::index_or_slice_expression: + switch(*p_) + { + case ']': + { + if (buffer.empty()) + { + push_token(token(jsoncons::make_unique()), ec); + if (ec) {return jmespath_expression();} + } + else + { + int64_t val{ 0 }; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), val); + if (!r) + { + ec = jmespath_errc::invalid_number; + return jmespath_expression(); + } + push_token(token(jsoncons::make_unique(val)), ec); + if (ec) {return jmespath_expression();} + + buffer.clear(); + } + state_stack_.pop_back(); // bracket_specifier + ++p_; + ++column_; + break; + } + case ':': + { + if (!buffer.empty()) + { + int64_t val; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), val); + if (!r) + { + ec = jmespath_errc::invalid_number; + return jmespath_expression(); + } + slic.start_ = val; + buffer.clear(); + } + state_stack_.back() = path_state::rhs_slice_expression_stop; + state_stack_.emplace_back(path_state::number); + ++p_; + ++column_; + break; + } + default: + ec = jmespath_errc::expected_rbracket; + return jmespath_expression(); + } + break; + case path_state::rhs_slice_expression_stop : + { + if (!buffer.empty()) + { + int64_t val{ 0 }; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), val); + if (!r) + { + ec = jmespath_errc::invalid_number; + return jmespath_expression(); + } + slic.stop_ = jsoncons::optional(val); + buffer.clear(); + } + switch(*p_) + { + case ']': + push_token(token(jsoncons::make_unique(slic)), ec); + if (ec) {return jmespath_expression();} + slic = slice{}; + state_stack_.pop_back(); // bracket_specifier2 + ++p_; + ++column_; + break; + case ':': + state_stack_.back() = path_state::rhs_slice_expression_step; + state_stack_.emplace_back(path_state::number); + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_rbracket; + return jmespath_expression(); + } + break; + } + case path_state::rhs_slice_expression_step: + { + if (!buffer.empty()) + { + int64_t val{ 0 }; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), val); + if (!r) + { + ec = jmespath_errc::invalid_number; + return jmespath_expression(); + } + if (val == 0) + { + ec = jmespath_errc::step_cannot_be_zero; + return jmespath_expression(); + } + slic.step_ = val; + buffer.clear(); + } + switch(*p_) + { + case ']': + push_token(token(jsoncons::make_unique(slic)), ec); + if (ec) {return jmespath_expression();} + buffer.clear(); + slic = slice{}; + state_stack_.pop_back(); // rhs_slice_expression_step + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_rbracket; + return jmespath_expression(); + } + break; + } + case path_state::expect_rbracket: + { + switch(*p_) + { + case ']': + state_stack_.pop_back(); // expect_rbracket + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_rbracket; + return jmespath_expression(); + } + break; + } + case path_state::expect_rparen: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ')': + ++p_; + ++column_; + push_token(rparen_arg, ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::rhs_expression; + break; + default: + ec = jmespath_errc::expected_rparen; + return jmespath_expression(); + } + break; + case path_state::key_val_expr: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '\"': + state_stack_.back() = path_state::expect_colon; + state_stack_.emplace_back(path_state::key_expr); + state_stack_.emplace_back(path_state::quoted_string); + ++p_; + ++column_; + break; + case '\'': + state_stack_.back() = path_state::expect_colon; + state_stack_.emplace_back(path_state::raw_string); + ++p_; + ++column_; + break; + default: + if ((*p_ >= 'A' && *p_ <= 'Z') || (*p_ >= 'a' && *p_ <= 'z') || (*p_ == '_')) + { + state_stack_.back() = path_state::expect_colon; + state_stack_.emplace_back(path_state::key_expr); + state_stack_.emplace_back(path_state::unquoted_string); + buffer.push_back(*p_); + ++p_; + ++column_; + } + else + { + ec = jmespath_errc::expected_key; + return jmespath_expression(); + } + break; + }; + break; + } + case path_state::cmp_lt_or_lte: + { + switch(*p_) + { + case '=': + push_token(token(resources_.get_lte_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + push_token(token(resources_.get_lt_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + } + break; + } + case path_state::cmp_gt_or_gte: + { + switch(*p_) + { + case '=': + push_token(token(resources_.get_gte_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + push_token(token(resources_.get_gt_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + } + break; + } + case path_state::cmp_eq: + { + switch(*p_) + { + case '=': + push_token(token(resources_.get_eq_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_comparator; + return jmespath_expression(); + } + break; + } + case path_state::cmp_ne: + { + switch(*p_) + { + case '=': + push_token(token(resources_.get_ne_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_comparator; + return jmespath_expression(); + } + break; + } + case path_state::expect_dot: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '.': + state_stack_.pop_back(); // expect_dot + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_dot; + return jmespath_expression(); + } + break; + } + case path_state::expect_pipe_or_or: + { + switch(*p_) + { + case '|': + push_token(token(resources_.get_or_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + push_token(token(pipe_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + } + break; + } + case path_state::expect_and: + { + switch(*p_) + { + case '&': + push_token(token(resources_.get_and_operator()), ec); + push_token(token(current_node_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); // expect_and + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_and; + return jmespath_expression(); + } + break; + } + case path_state::multi_select_list: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + push_token(token(separator_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.emplace_back(path_state::lhs_expression); + ++p_; + ++column_; + break; + case '[': + state_stack_.emplace_back(path_state::lhs_expression); + break; + case '.': + state_stack_.emplace_back(path_state::sub_expression); + ++p_; + ++column_; + break; + case '|': + { + ++p_; + ++column_; + state_stack_.emplace_back(path_state::lhs_expression); + state_stack_.emplace_back(path_state::expect_pipe_or_or); + break; + } + case ']': + { + push_token(token(end_multi_select_list_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + + ++p_; + ++column_; + break; + } + default: + ec = jmespath_errc::expected_rbracket; + return jmespath_expression(); + } + break; + } + case path_state::filter: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + { + push_token(token(end_filter_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + } + default: + ec = jmespath_errc::expected_rbracket; + return jmespath_expression(); + } + break; + } + case path_state::expect_rbrace: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + push_token(token(separator_arg), ec); + if (ec) {return jmespath_expression();} + state_stack_.back() = path_state::key_val_expr; + ++p_; + ++column_; + break; + case '[': + case '{': + state_stack_.emplace_back(path_state::lhs_expression); + break; + case '.': + state_stack_.emplace_back(path_state::sub_expression); + ++p_; + ++column_; + break; + case '}': + { + state_stack_.pop_back(); + push_token(end_multi_select_hash_arg, ec); + if (ec) {return jmespath_expression();} + ++p_; + ++column_; + break; + } + default: + ec = jmespath_errc::expected_rbrace; + return jmespath_expression(); + } + break; + } + case path_state::expect_colon: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ':': + state_stack_.back() = path_state::expect_rbrace; + state_stack_.emplace_back(path_state::lhs_expression); + ++p_; + ++column_; + break; + default: + ec = jmespath_errc::expected_colon; + return jmespath_expression(); + } + break; + } + } + + } + + if (state_stack_.empty()) + { + ec = jmespath_errc::syntax_error; + return jmespath_expression(); + } + while (state_stack_.size() > 1) + { + switch (state_stack_.back()) + { + case path_state::rhs_expression: + if (state_stack_.size() > 1) + { + state_stack_.pop_back(); + } + else + { + ec = jmespath_errc::syntax_error; + return jmespath_expression(); + } + break; + case path_state::val_expr: + push_token(token(jsoncons::make_unique(buffer)), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + case path_state::identifier_or_function_expr: + push_token(token(jsoncons::make_unique(buffer)), ec); + if (ec) {return jmespath_expression();} + state_stack_.pop_back(); + break; + case path_state::unquoted_string: + state_stack_.pop_back(); + break; + default: + ec = jmespath_errc::syntax_error; + return jmespath_expression(); + break; + } + } + + if (!(state_stack_.size() == 1 && state_stack_.back() == path_state::rhs_expression)) + { + ec = jmespath_errc::unexpected_end_of_input; + return jmespath_expression(); + } + + state_stack_.pop_back(); + + push_token(end_of_expression_arg, ec); + if (ec) {return jmespath_expression();} + + //for (auto& t : output_stack_) + //{ + // std::cout << t.to_string() << std::endl; + //} + + return jmespath_expression(std::move(resources_), std::move(output_stack_)); + } + + void advance_past_space_character() + { + switch (*p_) + { + case ' ':case '\t': + ++p_; + ++column_; + break; + case '\r': + if (p_+1 < end_input_) + { + if (*(p_ + 1) == '\n') + ++p_; + } + ++line_; + column_ = 1; + ++p_; + break; + case '\n': + ++line_; + column_ = 1; + ++p_; + break; + default: + break; + } + } + + void unwind_rparen(std::error_code& ec) + { + auto it = operator_stack_.rbegin(); + while (it != operator_stack_.rend() && !it->is_lparen()) + { + output_stack_.emplace_back(std::move(*it)); + ++it; + } + if (it == operator_stack_.rend()) + { + ec = jmespath_errc::unbalanced_parentheses; + return; + } + ++it; + operator_stack_.erase(it.base(),operator_stack_.end()); + } + + void push_token(token&& tok, std::error_code& ec) + { + switch (tok.type()) + { + case token_kind::end_filter: + { + unwind_rparen(ec); + std::vector toks; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->type() != token_kind::begin_filter) + { + toks.emplace_back(std::move(*it)); + ++it; + } + if (it == output_stack_.rend()) + { + ec = jmespath_errc::unbalanced_braces; + return; + } + if (toks.back().type() != token_kind::literal) + { + toks.emplace_back(current_node_arg); + } + std::reverse(toks.begin(), toks.end()); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + + if (!output_stack_.empty() && output_stack_.back().is_projection() && + (tok.precedence_level() < output_stack_.back().precedence_level() || + (tok.precedence_level() == output_stack_.back().precedence_level() && tok.is_right_associative()))) + { + output_stack_.back().expression_->add_expression(jsoncons::make_unique(std::move(toks))); + } + else + { + output_stack_.emplace_back(token(jsoncons::make_unique(std::move(toks)))); + } + break; + } + case token_kind::end_multi_select_list: + { + unwind_rparen(ec); + std::vector> vals; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->type() != token_kind::begin_multi_select_list) + { + std::vector toks; + do + { + toks.emplace_back(std::move(*it)); + ++it; + } while (it != output_stack_.rend() && it->type() != token_kind::begin_multi_select_list && it->type() != token_kind::separator); + if (it->type() == token_kind::separator) + { + ++it; + } + if (toks.back().type() != token_kind::literal) + { + toks.emplace_back(current_node_arg); + } + std::reverse(toks.begin(), toks.end()); + vals.emplace_back(std::move(toks)); + } + if (it == output_stack_.rend()) + { + ec = jmespath_errc::unbalanced_braces; + return; + } + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + std::reverse(vals.begin(), vals.end()); + if (!output_stack_.empty() && output_stack_.back().is_projection() && + (tok.precedence_level() < output_stack_.back().precedence_level() || + (tok.precedence_level() == output_stack_.back().precedence_level() && tok.is_right_associative()))) + { + output_stack_.back().expression_->add_expression(jsoncons::make_unique(std::move(vals))); + } + else + { + output_stack_.emplace_back(token(jsoncons::make_unique(std::move(vals)))); + } + break; + } + case token_kind::end_multi_select_hash: + { + unwind_rparen(ec); + std::vector key_toks; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->type() != token_kind::begin_multi_select_hash) + { + std::vector toks; + do + { + toks.emplace_back(std::move(*it)); + ++it; + } while (it != output_stack_.rend() && it->type() != token_kind::key); + JSONCONS_ASSERT(it->is_key()); + auto key = std::move(it->key_); + ++it; + if (it->type() == token_kind::separator) + { + ++it; + } + if (toks.back().type() != token_kind::literal) + { + toks.emplace_back(current_node_arg); + } + std::reverse(toks.begin(), toks.end()); + key_toks.emplace_back(std::move(key), std::move(toks)); + } + if (it == output_stack_.rend()) + { + ec = jmespath_errc::unbalanced_braces; + return; + } + std::reverse(key_toks.begin(), key_toks.end()); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + + if (!output_stack_.empty() && output_stack_.back().is_projection() && + (tok.precedence_level() < output_stack_.back().precedence_level() || + (tok.precedence_level() == output_stack_.back().precedence_level() && tok.is_right_associative()))) + { + output_stack_.back().expression_->add_expression(jsoncons::make_unique(std::move(key_toks))); + } + else + { + output_stack_.emplace_back(token(jsoncons::make_unique(std::move(key_toks)))); + } + break; + } + case token_kind::end_expression_type: + { + std::vector toks; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->type() != token_kind::begin_expression_type) + { + toks.emplace_back(std::move(*it)); + ++it; + } + if (it == output_stack_.rend()) + { + JSONCONS_THROW(json_runtime_error("Unbalanced braces")); + } + if (toks.back().type() != token_kind::literal) + { + toks.emplace_back(current_node_arg); + } + std::reverse(toks.begin(), toks.end()); + output_stack_.erase(it.base(),output_stack_.end()); + output_stack_.emplace_back(token(jsoncons::make_unique(std::move(toks)))); + break; + } + case token_kind::literal: + if (!output_stack_.empty() && output_stack_.back().type() == token_kind::current_node) + { + output_stack_.back() = std::move(tok); + } + else + { + output_stack_.emplace_back(std::move(tok)); + } + break; + case token_kind::expression: + if (!output_stack_.empty() && output_stack_.back().is_projection() && + (tok.precedence_level() < output_stack_.back().precedence_level() || + (tok.precedence_level() == output_stack_.back().precedence_level() && tok.is_right_associative()))) + { + output_stack_.back().expression_->add_expression(std::move(tok.expression_)); + } + else + { + output_stack_.emplace_back(std::move(tok)); + } + break; + case token_kind::rparen: + { + unwind_rparen(ec); + break; + } + case token_kind::end_function: + { + unwind_rparen(ec); + std::vector toks; + auto it = output_stack_.rbegin(); + std::size_t arg_count = 0; + while (it != output_stack_.rend() && it->type() != token_kind::function) + { + if (it->type() == token_kind::argument) + { + ++arg_count; + } + toks.emplace_back(std::move(*it)); + ++it; + } + if (it == output_stack_.rend()) + { + ec = jmespath_errc::unbalanced_parentheses; + return; + } + if (it->arity() && arg_count != *(it->arity())) + { + ec = jmespath_errc::invalid_arity; + return; + } + if (toks.back().type() != token_kind::literal) + { + toks.emplace_back(current_node_arg); + } + std::reverse(toks.begin(), toks.end()); + toks.push_back(std::move(*it)); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + + if (!output_stack_.empty() && output_stack_.back().is_projection() && + (tok.precedence_level() < output_stack_.back().precedence_level() || + (tok.precedence_level() == output_stack_.back().precedence_level() && tok.is_right_associative()))) + { + output_stack_.back().expression_->add_expression(jsoncons::make_unique(std::move(toks))); + } + else + { + output_stack_.emplace_back(token(jsoncons::make_unique(std::move(toks)))); + } + break; + } + case token_kind::end_of_expression: + { + auto it = operator_stack_.rbegin(); + while (it != operator_stack_.rend()) + { + output_stack_.emplace_back(std::move(*it)); + ++it; + } + operator_stack_.clear(); + break; + } + case token_kind::unary_operator: + case token_kind::binary_operator: + { + if (operator_stack_.empty() || operator_stack_.back().is_lparen()) + { + operator_stack_.emplace_back(std::move(tok)); + } + else if (tok.precedence_level() < operator_stack_.back().precedence_level() + || (tok.precedence_level() == operator_stack_.back().precedence_level() && tok.is_right_associative())) + { + operator_stack_.emplace_back(std::move(tok)); + } + else + { + auto it = operator_stack_.rbegin(); + while (it != operator_stack_.rend() && it->is_operator() + && (tok.precedence_level() > it->precedence_level() + || (tok.precedence_level() == it->precedence_level() && tok.is_right_associative()))) + { + output_stack_.emplace_back(std::move(*it)); + ++it; + } + + operator_stack_.erase(it.base(),operator_stack_.end()); + operator_stack_.emplace_back(std::move(tok)); + } + break; + } + case token_kind::separator: + { + unwind_rparen(ec); + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token(lparen_arg)); + break; + } + case token_kind::begin_filter: + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token(lparen_arg)); + break; + case token_kind::begin_multi_select_list: + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token(lparen_arg)); + break; + case token_kind::begin_multi_select_hash: + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token(lparen_arg)); + break; + case token_kind::function: + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token(lparen_arg)); + break; + case token_kind::current_node: + output_stack_.emplace_back(std::move(tok)); + break; + case token_kind::key: + case token_kind::pipe: + case token_kind::argument: + case token_kind::begin_expression_type: + output_stack_.emplace_back(std::move(tok)); + break; + case token_kind::lparen: + operator_stack_.emplace_back(std::move(tok)); + break; + default: + break; + } + } + + uint32_t append_to_codepoint(uint32_t cp, int c, std::error_code& ec) + { + cp *= 16; + if (c >= '0' && c <= '9') + { + cp += c - '0'; + } + else if (c >= 'a' && c <= 'f') + { + cp += c - 'a' + 10; + } + else if (c >= 'A' && c <= 'F') + { + cp += c - 'A' + 10; + } + else + { + ec = jmespath_errc::invalid_codepoint; + } + return cp; + } + }; + + } // detail + + template + using jmespath_expression = typename jsoncons::jmespath::detail::jmespath_evaluator::jmespath_expression; + + template + Json search(const Json& doc, const typename Json::string_view_type& path) + { + jsoncons::jmespath::detail::jmespath_evaluator evaluator; + std::error_code ec; + auto expr = evaluator.compile(path.data(), path.size(), ec); + if (ec) + { + JSONCONS_THROW(jmespath_error(ec, evaluator.line(), evaluator.column())); + } + auto result = expr.evaluate(doc, ec); + if (ec) + { + JSONCONS_THROW(jmespath_error(ec)); + } + return result; + } + + template + Json search(const Json& doc, const typename Json::string_view_type& path, std::error_code& ec) + { + jsoncons::jmespath::detail::jmespath_evaluator evaluator; + auto expr = evaluator.compile(path.data(), path.size(), ec); + if (ec) + { + return Json::null(); + } + auto result = expr.evaluate(doc, ec); + if (ec) + { + return Json::null(); + } + return result; + } + + template + jmespath_expression make_expression(const typename json::string_view_type& expr) + { + return jmespath_expression::compile(expr); + } + + template + jmespath_expression make_expression(const typename json::string_view_type& expr, + std::error_code& ec) + { + return jmespath_expression::compile(expr, ec); + } + + +} // namespace jmespath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jmespath/jmespath_error.hpp b/third_party/jsoncons_ext/jmespath/jmespath_error.hpp new file mode 100644 index 0000000000..b9f685e3da --- /dev/null +++ b/third_party/jsoncons_ext/jmespath/jmespath_error.hpp @@ -0,0 +1,215 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JMESPATH_JMESPATH_ERROR_HPP +#define JSONCONS_JMESPATH_JMESPATH_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace jmespath { + + class jmespath_error : public std::system_error, public virtual json_exception + { + std::size_t line_number_; + std::size_t column_number_; + mutable std::string what_; + public: + jmespath_error(std::error_code ec) + : std::system_error(ec), line_number_(0), column_number_(0) + { + } + jmespath_error(std::error_code ec, const std::string& what_arg) + : std::system_error(ec, what_arg), line_number_(0), column_number_(0) + { + } + jmespath_error(std::error_code ec, std::size_t position) + : std::system_error(ec), line_number_(0), column_number_(position) + { + } + jmespath_error(std::error_code ec, std::size_t line, std::size_t column) + : std::system_error(ec), line_number_(line), column_number_(column) + { + } + jmespath_error(const jmespath_error& other) = default; + + jmespath_error(jmespath_error&& other) = default; + + const char* what() const noexcept override + { + if (what_.empty()) + { + JSONCONS_TRY + { + what_.append(std::system_error::what()); + if (line_number_ != 0 && column_number_ != 0) + { + what_.append(" at line "); + what_.append(std::to_string(line_number_)); + what_.append(" and column "); + what_.append(std::to_string(column_number_)); + } + else if (column_number_ != 0) + { + what_.append(" at position "); + what_.append(std::to_string(column_number_)); + } + return what_.c_str(); + } + JSONCONS_CATCH(...) + { + return std::system_error::what(); + } + } + else + { + return what_.c_str(); + } + } + + std::size_t line() const noexcept + { + return line_number_; + } + + std::size_t column() const noexcept + { + return column_number_; + } + }; + +enum class jmespath_errc +{ + success = 0, + expected_identifier, + expected_index, + expected_A_Za_Z_, + expected_rbracket, + expected_rparen, + expected_rbrace, + expected_colon, + expected_dot, + expected_or, + expected_and, + expected_multi_select_list, + invalid_number, + invalid_literal, + expected_comparator, + expected_key, + invalid_argument, + unknown_function, + invalid_type, + unexpected_end_of_input, + step_cannot_be_zero, + syntax_error, + invalid_codepoint, + illegal_escaped_character, + unbalanced_parentheses, + unbalanced_braces, + invalid_arity, + identifier_not_found, + expected_index_expression, + unknown_error +}; + +class jmespath_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/jmespath"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case jmespath_errc::expected_identifier: + return "Expected identifier"; + case jmespath_errc::expected_index: + return "Expected index"; + case jmespath_errc::expected_A_Za_Z_: + return "Expected A-Z, a-z, or _"; + case jmespath_errc::expected_rbracket: + return "Expected ]"; + case jmespath_errc::expected_rparen: + return "Expected )"; + case jmespath_errc::expected_rbrace: + return "Expected }"; + case jmespath_errc::expected_colon: + return "Expected :"; + case jmespath_errc::expected_dot: + return "Expected \".\""; + case jmespath_errc::expected_or: + return "Expected \"||\""; + case jmespath_errc::expected_and: + return "Expected \"&&\""; + case jmespath_errc::expected_multi_select_list: + return "Expected multi-select-list"; + case jmespath_errc::invalid_number: + return "Invalid number"; + case jmespath_errc::invalid_literal: + return "Invalid literal"; + case jmespath_errc::expected_comparator: + return "Expected <, <=, ==, >=, > or !="; + case jmespath_errc::expected_key: + return "Expected key"; + case jmespath_errc::invalid_argument: + return "Invalid argument type"; + case jmespath_errc::unknown_function: + return "Unknown function"; + case jmespath_errc::invalid_type: + return "Invalid type"; + case jmespath_errc::unexpected_end_of_input: + return "Unexpected end of jmespath input"; + case jmespath_errc::step_cannot_be_zero: + return "Slice step cannot be zero"; + case jmespath_errc::syntax_error: + return "Syntax error"; + case jmespath_errc::invalid_codepoint: + return "Invalid codepoint"; + case jmespath_errc::illegal_escaped_character: + return "Illegal escaped character"; + case jmespath_errc::unbalanced_parentheses: + return "Unbalanced parentheses"; + case jmespath_errc::unbalanced_braces: + return "Unbalanced braces"; + case jmespath_errc::invalid_arity: + return "Function called with wrong number of arguments"; + case jmespath_errc::identifier_not_found: + return "Identifier not found"; + case jmespath_errc::expected_index_expression: + return "Expected index expression"; + case jmespath_errc::unknown_error: + default: + return "Unknown jmespath parser error"; + } + } +}; + +inline +const std::error_category& jmespath_error_category() +{ + static jmespath_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(jmespath_errc result) +{ + return std::error_code(static_cast(result),jmespath_error_category()); +} + +}} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons_ext/jsonpatch/jsonpatch.hpp b/third_party/jsoncons_ext/jsonpatch/jsonpatch.hpp new file mode 100644 index 0000000000..23f39cbff9 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpatch/jsonpatch.hpp @@ -0,0 +1,585 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATCH_JSONPATCH_HPP +#define JSONCONS_JSONPATCH_JSONPATCH_HPP + +#include +#include +#include +#include // std::min +#include // std::move +#include +#include +#include + +namespace jsoncons { namespace jsonpatch { + +namespace detail { + + template + struct jsonpatch_names + { + static std::basic_string test_name() + { + static std::basic_string name{'t','e','s','t'}; + return name; + } + static std::basic_string add_name() + { + static std::basic_string name{'a','d','d'}; + return name; + } + static std::basic_string remove_name() + { + static std::basic_string name{'r','e','m','o','v','e'}; + return name; + } + static std::basic_string replace_name() + { + static std::basic_string name{'r','e','p','l','a','c','e'}; + return name; + } + static std::basic_string move_name() + { + static std::basic_string name{'m','o','v','e'}; + return name; + } + static std::basic_string copy_name() + { + static std::basic_string name{'c','o','p','y'}; + return name; + } + static std::basic_string op_name() + { + static std::basic_string name{'o','p'}; + return name; + } + static std::basic_string path_name() + { + static std::basic_string name{'p','a','t','h'}; + return name; + } + static std::basic_string from_name() + { + static std::basic_string name{'f','r','o','m'}; + return name; + } + static std::basic_string value_name() + { + static std::basic_string name{'v','a','l','u','e'}; + return name; + } + static std::basic_string dash_name() + { + static std::basic_string name{'-'}; + return name; + } + }; + + template + jsonpointer::basic_json_pointer definite_path(const Json& root, jsonpointer::basic_json_pointer& location) + { + using char_type = typename Json::char_type; + using string_type = std::basic_string; + + auto rit = location.rbegin(); + if (rit == location.rend()) + { + return location; + } + + if (*rit != jsonpatch_names::dash_name()) + { + return location; + } + + std::vector tokens; + for (auto it = location.begin(); it != location.rbegin().base()-1; ++it) + { + tokens.push_back(*it); + } + jsonpointer::basic_json_pointer pointer(tokens); + + std::error_code ec; + + Json val = jsonpointer::get(root, pointer, ec); + if (ec || !val.is_array()) + { + return location; + } + string_type last_token; + jsoncons::detail::from_integer(val.size(), last_token); + tokens.emplace_back(std::move(last_token)); + + return jsonpointer::basic_json_pointer(std::move(tokens)); + } + + enum class op_type {add,remove,replace}; + enum class state_type {begin,abort,commit}; + + template + struct operation_unwinder + { + using char_type = typename Json::char_type; + using string_type = std::basic_string; + using json_pointer_type = jsonpointer::basic_json_pointer; + + struct entry + { + op_type op; + json_pointer_type path; + Json value; + + entry(op_type Op, const json_pointer_type& Path, const Json& Value) + : op(Op), path(Path), value(Value) + { + } + + entry(const entry&) = default; + + entry(entry&&) = default; + + entry& operator=(const entry&) = default; + + entry& operator=(entry&&) = default; + }; + + Json& target; + state_type state; + std::vector stack; + + operation_unwinder(Json& j) + : target(j), state(state_type::begin) + { + } + + ~operation_unwinder() noexcept + { + std::error_code ec; + if (state != state_type::commit) + { + for (auto it = stack.rbegin(); it != stack.rend(); ++it) + { + if (it->op == op_type::add) + { + jsonpointer::add(target,it->path,it->value,ec); + if (ec) + { + //std::cout << "add: " << it->path << std::endl; + break; + } + } + else if (it->op == op_type::remove) + { + jsonpointer::remove(target,it->path,ec); + if (ec) + { + //std::cout << "remove: " << it->path << std::endl; + break; + } + } + else if (it->op == op_type::replace) + { + jsonpointer::replace(target,it->path,it->value,ec); + if (ec) + { + //std::cout << "replace: " << it->path << std::endl; + break; + } + } + } + } + } + }; + + template + Json from_diff(const Json& source, const Json& target, const typename Json::string_view_type& path) + { + using char_type = typename Json::char_type; + + Json result = typename Json::array(); + + if (source == target) + { + return result; + } + + if (source.is_array() && target.is_array()) + { + std::size_t common = (std::min)(source.size(),target.size()); + for (std::size_t i = 0; i < common; ++i) + { + std::basic_string ss(path); + ss.push_back('/'); + jsoncons::detail::from_integer(i,ss); + auto temp_diff = from_diff(source[i],target[i],ss); + result.insert(result.array_range().end(),temp_diff.array_range().begin(),temp_diff.array_range().end()); + } + // Element in source, not in target - remove + for (std::size_t i = source.size(); i-- > target.size();) + { + std::basic_string ss(path); + ss.push_back('/'); + jsoncons::detail::from_integer(i,ss); + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::remove_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + result.push_back(std::move(val)); + } + // Element in target, not in source - add, + // Fix contributed by Alexander rog13 + for (std::size_t i = source.size(); i < target.size(); ++i) + { + const auto& a = target[i]; + std::basic_string ss(path); + ss.push_back('/'); + jsoncons::detail::from_integer(i,ss); + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::add_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + val.insert_or_assign(jsonpatch_names::value_name(), a); + result.push_back(std::move(val)); + } + } + else if (source.is_object() && target.is_object()) + { + for (const auto& a : source.object_range()) + { + std::basic_string ss(path); + ss.push_back('/'); + jsonpointer::escape(a.key(),ss); + auto it = target.find(a.key()); + if (it != target.object_range().end()) + { + auto temp_diff = from_diff(a.value(),it->value(),ss); + result.insert(result.array_range().end(),temp_diff.array_range().begin(),temp_diff.array_range().end()); + } + else + { + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::remove_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + result.push_back(std::move(val)); + } + } + for (const auto& a : target.object_range()) + { + auto it = source.find(a.key()); + if (it == source.object_range().end()) + { + std::basic_string ss(path); + ss.push_back('/'); + jsonpointer::escape(a.key(),ss); + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::add_name()); + val.insert_or_assign(jsonpatch_names::path_name(), ss); + val.insert_or_assign(jsonpatch_names::value_name(), a.value()); + result.push_back(std::move(val)); + } + } + } + else + { + Json val(json_object_arg); + val.insert_or_assign(jsonpatch_names::op_name(), jsonpatch_names::replace_name()); + val.insert_or_assign(jsonpatch_names::path_name(), path); + val.insert_or_assign(jsonpatch_names::value_name(), target); + result.push_back(std::move(val)); + } + + return result; + } +} + +template +void apply_patch(Json& target, const Json& patch, std::error_code& ec) +{ + if (!patch.is_array()) + { + ec = jsonpatch_errc::invalid_patch; + return; + } + + using char_type = typename Json::char_type; + using string_type = std::basic_string; + using json_pointer_type = jsonpointer::basic_json_pointer; + + jsoncons::jsonpatch::detail::operation_unwinder unwinder(target); + std::error_code local_ec; + + // Validate + + for (const auto& operation : patch.array_range()) + { + unwinder.state =jsoncons::jsonpatch::detail::state_type::begin; + + auto it_op = operation.find(detail::jsonpatch_names::op_name()); + if (it_op == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type op = it_op->value().template as(); + + auto it_path = operation.find(detail::jsonpatch_names::path_name()); + if (it_path == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type path = it_path->value().template as(); + auto location = json_pointer_type::parse(path, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + + if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::test_name()) + { + Json val = jsonpointer::get(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::test_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + auto it_value = operation.find(detail::jsonpatch_names::value_name()); + if (it_value == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + if (val != it_value->value()) + { + ec = jsonpatch_errc::test_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::add_name()) + { + auto it_value = operation.find(detail::jsonpatch_names::value_name()); + if (it_value == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + Json val = it_value->value(); + auto npath = jsonpatch::detail::definite_path(target,location); + + std::error_code insert_ec; + jsonpointer::add_if_absent(target,npath,val,insert_ec); // try insert without replace + if (insert_ec) // try a replace + { + std::error_code select_ec; + Json orig_val = jsonpointer::get(target,npath,select_ec); + if (select_ec) // shouldn't happen + { + ec = jsonpatch_errc::add_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + std::error_code replace_ec; + jsonpointer::replace(target,npath,val,replace_ec); + if (replace_ec) + { + ec = jsonpatch_errc::add_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::replace,npath,orig_val); + } + else // insert without replace succeeded + { + unwinder.stack.emplace_back(detail::op_type::remove,npath,Json::null()); + } + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::remove_name()) + { + Json val = jsonpointer::get(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::remove_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + jsonpointer::remove(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::remove_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::add, location, val); + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::replace_name()) + { + Json val = jsonpointer::get(target,location,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::replace_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + auto it_value = operation.find(detail::jsonpatch_names::value_name()); + if (it_value == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + jsonpointer::replace(target, location, it_value->value(), local_ec); + if (local_ec) + { + ec = jsonpatch_errc::replace_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::replace,location,val); + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::move_name()) + { + auto it_from = operation.find(detail::jsonpatch_names::from_name()); + if (it_from == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type from = it_from->value().as_string(); + auto from_pointer = json_pointer_type::parse(from, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::move_failed; + unwinder.state = jsoncons::jsonpatch::detail::state_type::abort; + return; + } + + Json val = jsonpointer::get(target, from_pointer, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::move_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + jsonpointer::remove(target, from_pointer, local_ec); + if (local_ec) + { + ec = jsonpatch_errc::move_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(detail::op_type::add, from_pointer, val); + // add + std::error_code insert_ec; + auto npath = jsonpatch::detail::definite_path(target,location); + jsonpointer::add_if_absent(target,npath,val,insert_ec); // try insert without replace + if (insert_ec) // try a replace + { + std::error_code select_ec; + Json orig_val = jsonpointer::get(target,npath,select_ec); + if (select_ec) // shouldn't happen + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + std::error_code replace_ec; + jsonpointer::replace(target, npath, val, replace_ec); + if (replace_ec) + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(jsoncons::jsonpatch::detail::op_type::replace,npath,orig_val); + } + else + { + unwinder.stack.emplace_back(detail::op_type::remove,npath,Json::null()); + } + } + else if (op ==jsoncons::jsonpatch::detail::jsonpatch_names::copy_name()) + { + auto it_from = operation.find(detail::jsonpatch_names::from_name()); + if (it_from == operation.object_range().end()) + { + ec = jsonpatch_errc::invalid_patch; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + string_type from = it_from->value().as_string(); + Json val = jsonpointer::get(target,from,local_ec); + if (local_ec) + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + // add + auto npath = jsonpatch::detail::definite_path(target,location); + std::error_code insert_ec; + jsonpointer::add_if_absent(target,npath,val,insert_ec); // try insert without replace + if (insert_ec) // Failed, try a replace + { + std::error_code select_ec; + Json orig_val = jsonpointer::get(target,npath, select_ec); + if (select_ec) // shouldn't happen + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + std::error_code replace_ec; + jsonpointer::replace(target, npath, val,replace_ec); + if (replace_ec) + { + ec = jsonpatch_errc::copy_failed; + unwinder.state =jsoncons::jsonpatch::detail::state_type::abort; + return; + } + unwinder.stack.emplace_back(jsoncons::jsonpatch::detail::op_type::replace,npath,orig_val); + } + else + { + unwinder.stack.emplace_back(detail::op_type::remove,npath,Json::null()); + } + } + } + if (unwinder.state ==jsoncons::jsonpatch::detail::state_type::begin) + { + unwinder.state =jsoncons::jsonpatch::detail::state_type::commit; + } +} + +template +Json from_diff(const Json& source, const Json& target) +{ + std::basic_string path; + return jsoncons::jsonpatch::detail::from_diff(source, target, path); +} + +template +void apply_patch(Json& target, const Json& patch) +{ + std::error_code ec; + apply_patch(target, patch, ec); + if (ec) + { + JSONCONS_THROW(jsonpatch_error(ec)); + } +} + +}} + +#endif diff --git a/third_party/jsoncons_ext/jsonpatch/jsonpatch_error.hpp b/third_party/jsoncons_ext/jsonpatch/jsonpatch_error.hpp new file mode 100644 index 0000000000..d7240c34f4 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpatch/jsonpatch_error.hpp @@ -0,0 +1,121 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATCH_JSONPATCH_ERROR_HPP +#define JSONCONS_JSONPATCH_JSONPATCH_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace jsonpatch { + + enum class jsonpatch_errc + { + success = 0, + invalid_patch = 1, + test_failed, + add_failed, + remove_failed, + replace_failed, + move_failed, + copy_failed + + }; + + class jsonpatch_error_category_impl + : public std::error_category + { + public: + const char* name() const noexcept override + { + return "jsoncons/jsonpatch"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case jsonpatch_errc::invalid_patch: + return "Invalid JSON Patch document"; + case jsonpatch_errc::test_failed: + return "JSON Patch test operation failed"; + case jsonpatch_errc::add_failed: + return "JSON Patch add operation failed"; + case jsonpatch_errc::remove_failed: + return "JSON Patch remove operation failed"; + case jsonpatch_errc::replace_failed: + return "JSON Patch replace operation failed"; + case jsonpatch_errc::move_failed: + return "JSON Patch move operation failed"; + case jsonpatch_errc::copy_failed: + return "JSON Patch copy operation failed"; + default: + return "Unknown JSON Patch error"; + } + } + }; + + inline + const std::error_category& jsonpatch_error_category() + { + static jsonpatch_error_category_impl instance; + return instance; + } + + inline + std::error_code make_error_code(jsonpatch_errc result) + { + return std::error_code(static_cast(result),jsonpatch_error_category()); + } + +} // jsonpatch +} // jsoncons + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +namespace jsoncons { namespace jsonpatch { + +// allow to disable exceptions +#if !defined(JSONCONS_NO_EXCEPTIONS) + #define JSONCONS_THROW(exception) throw exception + #define JSONCONS_RETHROW throw + #define JSONCONS_TRY try + #define JSONCONS_CATCH(exception) catch(exception) +#else + #define JSONCONS_THROW(exception) std::terminate() + #define JSONCONS_RETHROW std::terminate() + #define JSONCONS_TRY if (true) + #define JSONCONS_CATCH(exception) if (false) +#endif + + class jsonpatch_error : public std::system_error, public virtual json_exception + { + public: + jsonpatch_error(const std::error_code& ec) + : std::system_error(ec) + { + } + + jsonpatch_error(const jsonpatch_error& other) = default; + + jsonpatch_error(jsonpatch_error&& other) = default; + + jsonpatch_error& operator=(const jsonpatch_error& e) = default; + jsonpatch_error& operator=(jsonpatch_error&& e) = default; + + const char* what() const noexcept override + { + return std::system_error::what(); + } + }; +} // jsonpatch +} // jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/expression.hpp b/third_party/jsoncons_ext/jsonpath/expression.hpp new file mode 100644 index 0000000000..1b31ee26bc --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/expression.hpp @@ -0,0 +1,3424 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_EXPRESSION_HPP +#define JSONCONS_JSONPATH_EXPRESSION_HPP + +#include // std::basic_string +#include // std::vector +#include // std::unordered_map +#include // std::unordered_set +#include // std::numeric_limits +#include // std::set +#include // std::move +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif +#include +#include +#include + +namespace jsoncons { +namespace jsonpath { + + struct reference_arg_t + { + explicit reference_arg_t() = default; + }; + constexpr reference_arg_t reference_arg{}; + + struct const_reference_arg_t + { + explicit const_reference_arg_t() = default; + }; + constexpr const_reference_arg_t const_reference_arg{}; + + struct literal_arg_t + { + explicit literal_arg_t() = default; + }; + constexpr literal_arg_t literal_arg{}; + + struct end_of_expression_arg_t + { + explicit end_of_expression_arg_t() = default; + }; + constexpr end_of_expression_arg_t end_of_expression_arg{}; + + struct separator_arg_t + { + explicit separator_arg_t() = default; + }; + constexpr separator_arg_t separator_arg{}; + + struct lparen_arg_t + { + explicit lparen_arg_t() = default; + }; + constexpr lparen_arg_t lparen_arg{}; + + struct rparen_arg_t + { + explicit rparen_arg_t() = default; + }; + constexpr rparen_arg_t rparen_arg{}; + + struct begin_union_arg_t + { + explicit begin_union_arg_t() = default; + }; + constexpr begin_union_arg_t begin_union_arg{}; + + struct end_union_arg_t + { + explicit end_union_arg_t() = default; + }; + constexpr end_union_arg_t end_union_arg{}; + + struct begin_filter_arg_t + { + explicit begin_filter_arg_t() = default; + }; + constexpr begin_filter_arg_t begin_filter_arg{}; + + struct end_filter_arg_t + { + explicit end_filter_arg_t() = default; + }; + constexpr end_filter_arg_t end_filter_arg{}; + + struct begin_expression_arg_t + { + explicit begin_expression_arg_t() = default; + }; + constexpr begin_expression_arg_t begin_expression_arg{}; + + struct end_index_expression_arg_t + { + explicit end_index_expression_arg_t() = default; + }; + constexpr end_index_expression_arg_t end_index_expression_arg{}; + + struct end_argument_expression_arg_t + { + explicit end_argument_expression_arg_t() = default; + }; + constexpr end_argument_expression_arg_t end_argument_expression_arg{}; + + struct current_node_arg_t + { + explicit current_node_arg_t() = default; + }; + constexpr current_node_arg_t current_node_arg{}; + + struct root_node_arg_t + { + explicit root_node_arg_t() = default; + }; + constexpr root_node_arg_t root_node_arg{}; + + struct end_function_arg_t + { + explicit end_function_arg_t() = default; + }; + constexpr end_function_arg_t end_function_arg{}; + + struct argument_arg_t + { + explicit argument_arg_t() = default; + }; + constexpr argument_arg_t argument_arg{}; + + enum class result_options {value=0, nodups=1, sort=2, sort_descending=4, path=8}; + + inline result_options operator~(result_options a) + { + return static_cast(~static_cast(a)); + } + + inline result_options operator&(result_options a, result_options b) + { + return static_cast(static_cast(a) & static_cast(b)); + } + + inline result_options operator^(result_options a, result_options b) + { + return static_cast(static_cast(a) ^ static_cast(b)); + } + + inline result_options operator|(result_options a, result_options b) + { + return static_cast(static_cast(a) | static_cast(b)); + } + + inline result_options operator&=(result_options& a, result_options b) + { + a = a & b; + return a; + } + + inline result_options operator^=(result_options& a, result_options b) + { + a = a ^ b; + return a; + } + + inline result_options operator|=(result_options& a, result_options b) + { + a = a | b; + return a; + } + + template + class parameter; + + template + class value_or_pointer + { + public: + friend class parameter; + using value_type = Json; + using reference = JsonReference; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + private: + bool is_value_; + union + { + value_type val_; + pointer ptr_; + }; + public: + value_or_pointer(value_type&& val) + : is_value_(true), val_(std::move(val)) + { + } + + value_or_pointer(pointer ptr) + : is_value_(false), ptr_(std::move(ptr)) + { + } + + value_or_pointer(value_or_pointer&& other) noexcept + : is_value_(other.is_value_) + { + if (is_value_) + { + new(&val_)value_type(std::move(other.val_)); + } + else + { + ptr_ = other.ptr_; + } + } + + ~value_or_pointer() noexcept + { + if (is_value_) + { + val_.~value_type(); + } + } + + value_or_pointer& operator=(value_or_pointer&& other) noexcept + { + if (is_value_) + { + val_.~value_type(); + } + is_value_ = other.is_value_; + + if (is_value_) + { + new(&val_)value_type(std::move(other.val_)); + } + else + { + ptr_ = other.ptr_; + } + return *this; + } + + reference value() + { + return is_value_ ? val_ : *ptr_; + } + + pointer ptr() + { + return is_value_ ? &val_ : ptr_; + } + }; + + template + class parameter + { + using value_type = Json; + using reference = const Json&; + using pointer = const Json*; + private: + value_or_pointer data_; + public: + template + parameter(value_or_pointer&& data) noexcept + : data_(nullptr) + { + data_.is_value_ = data.is_value_; + if (data.is_value_) + { + data_.val_ = std::move(data.val_); + } + else + { + data_.ptr_ = data.ptr_; + } + } + + parameter(parameter&& other) noexcept = default; + + parameter& operator=(parameter&& other) noexcept = default; + + const Json& value() const + { + return data_.is_value_ ? data_.val_ : *data_.ptr_; + } + }; + + template + class custom_function + { + public: + using value_type = Json; + using char_type = typename Json::char_type; + using parameter_type = parameter; + using function_type = std::function, std::error_code& ec)>; + using string_type = typename Json::string_type; + + string_type function_name_; + optional arity_; + function_type f_; + + custom_function(const string_type& function_name, + const optional& arity, + const function_type& f) + : function_name_(function_name), + arity_(arity), + f_(f) + { + } + + custom_function(string_type&& function_name, + optional&& arity, + function_type&& f) + : function_name_(std::move(function_name)), + arity_(std::move(arity)), + f_(std::move(f)) + { + } + + custom_function(const custom_function&) = default; + + custom_function(custom_function&&) = default; + + const string_type& name() const + { + return function_name_; + } + + optional arity() const + { + return arity_; + } + + const function_type& function() const + { + return f_; + } + }; + + template + class custom_functions + { + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using value_type = Json; + using parameter_type = parameter; + using function_type = std::function, std::error_code& ec)>; + using const_iterator = typename std::vector>::const_iterator; + + std::vector> functions_; + public: + void register_function(const string_type& name, + jsoncons::optional arity, + const function_type& f) + { + functions_.emplace_back(name, arity, f); + } + + const_iterator begin() const + { + return functions_.begin(); + } + + const_iterator end() const + { + return functions_.end(); + } + }; + +namespace detail { + + template + class dynamic_resources; + + template + struct unary_operator + { + std::size_t precedence_level_; + bool is_right_associative_; + + unary_operator(std::size_t precedence_level, + bool is_right_associative) + : precedence_level_(precedence_level), + is_right_associative_(is_right_associative) + { + } + + virtual ~unary_operator() = default; + + std::size_t precedence_level() const + { + return precedence_level_; + } + bool is_right_associative() const + { + return is_right_associative_; + } + + virtual Json evaluate(JsonReference, + std::error_code&) const = 0; + }; + + template + bool is_false(const Json& val) + { + return ((val.is_array() && val.empty()) || + (val.is_object() && val.empty()) || + (val.is_string() && val.as_string_view().empty()) || + (val.is_bool() && !val.as_bool()) || + val.is_null()); + } + + template + bool is_true(const Json& val) + { + return !is_false(val); + } + + template + class unary_not_operator final : public unary_operator + { + public: + unary_not_operator() + : unary_operator(1, true) + {} + + Json evaluate(JsonReference val, + std::error_code&) const override + { + return is_false(val) ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + }; + + template + class unary_minus_operator final : public unary_operator + { + public: + unary_minus_operator() + : unary_operator(1, true) + {} + + Json evaluate(JsonReference val, + std::error_code&) const override + { + if (val.is_int64()) + { + return Json(-val.template as(), semantic_tag::none); + } + else if (val.is_double()) + { + return Json(-val.as_double(), semantic_tag::none); + } + else + { + return Json::null(); + } + } + }; + + template + class regex_operator final : public unary_operator + { + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + std::basic_regex pattern_; + public: + regex_operator(std::basic_regex&& pattern) + : unary_operator(2, true), + pattern_(std::move(pattern)) + { + } + + regex_operator(regex_operator&&) = default; + regex_operator& operator=(regex_operator&&) = default; + + Json evaluate(JsonReference val, + std::error_code&) const override + { + if (!val.is_string()) + { + return Json::null(); + } + return std::regex_search(val.as_string(), pattern_) ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + }; + + template + struct binary_operator + { + std::size_t precedence_level_; + bool is_right_associative_; + + binary_operator(std::size_t precedence_level, + bool is_right_associative = false) + : precedence_level_(precedence_level), + is_right_associative_(is_right_associative) + { + } + + std::size_t precedence_level() const + { + return precedence_level_; + } + bool is_right_associative() const + { + return is_right_associative_; + } + + virtual Json evaluate(JsonReference, + JsonReference, + + std::error_code&) const = 0; + + virtual std::string to_string(int = 0) const + { + return "binary operator"; + } + + protected: + ~binary_operator() = default; + }; + + // Implementations + + template + class or_operator final : public binary_operator + { + public: + or_operator() + : binary_operator(9) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (lhs.is_null() && rhs.is_null()) + { + return Json::null(); + } + if (!is_false(lhs)) + { + return lhs; + } + else + { + return rhs; + } + } + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + //s.append("\n"); + s.append(level*2, ' '); + } + s.append("or operator"); + return s; + } + }; + + template + class and_operator final : public binary_operator + { + public: + and_operator() + : binary_operator(8) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (is_true(lhs)) + { + return rhs; + } + else + { + return lhs; + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("and operator"); + return s; + } + }; + + template + class eq_operator final : public binary_operator + { + public: + eq_operator() + : binary_operator(6) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + return lhs == rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("equal operator"); + return s; + } + }; + + template + class ne_operator final : public binary_operator + { + public: + ne_operator() + : binary_operator(6) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + return lhs != rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("not equal operator"); + return s; + } + }; + + template + class lt_operator final : public binary_operator + { + public: + lt_operator() + : binary_operator(5) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (lhs.is_number() && rhs.is_number()) + { + return lhs < rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + else if (lhs.is_string() && rhs.is_string()) + { + return lhs < rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + return Json::null(); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("less than operator"); + return s; + } + }; + + template + class lte_operator final : public binary_operator + { + public: + lte_operator() + : binary_operator(5) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (lhs.is_number() && rhs.is_number()) + { + return lhs <= rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + else if (lhs.is_string() && rhs.is_string()) + { + return lhs <= rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + return Json::null(); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("less than or equal operator"); + return s; + } + }; + + template + class gt_operator final : public binary_operator + { + public: + gt_operator() + : binary_operator(5) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + //std::cout << "operator> lhs: " << lhs << ", rhs: " << rhs << "\n"; + + if (lhs.is_number() && rhs.is_number()) + { + return lhs > rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + else if (lhs.is_string() && rhs.is_string()) + { + return lhs > rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + return Json::null(); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("greater than operator"); + return s; + } + }; + + template + class gte_operator final : public binary_operator + { + public: + gte_operator() + : binary_operator(5) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (lhs.is_number() && rhs.is_number()) + { + return lhs >= rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + else if (lhs.is_string() && rhs.is_string()) + { + return lhs >= rhs ? Json(true, semantic_tag::none) : Json(false, semantic_tag::none); + } + return Json::null(); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("greater than or equal operator"); + return s; + } + }; + + template + class plus_operator final : public binary_operator + { + public: + plus_operator() + : binary_operator(4) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (!(lhs.is_number() && rhs.is_number())) + { + return Json::null(); + } + else if (lhs.is_int64() && rhs.is_int64()) + { + return Json(((lhs.template as() + rhs.template as())), semantic_tag::none); + } + else if (lhs.is_uint64() && rhs.is_uint64()) + { + return Json((lhs.template as() + rhs.template as()), semantic_tag::none); + } + else + { + return Json((lhs.as_double() + rhs.as_double()), semantic_tag::none); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("plus operator"); + return s; + } + }; + + template + class minus_operator final : public binary_operator + { + public: + minus_operator() + : binary_operator(4) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (!(lhs.is_number() && rhs.is_number())) + { + return Json::null(); + } + else if (lhs.is_int64() && rhs.is_int64()) + { + return Json(((lhs.template as() - rhs.template as())), semantic_tag::none); + } + else if (lhs.is_uint64() && rhs.is_uint64()) + { + return Json((lhs.template as() - rhs.template as()), semantic_tag::none); + } + else + { + return Json((lhs.as_double() - rhs.as_double()), semantic_tag::none); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("minus operator"); + return s; + } + }; + + template + class mult_operator final : public binary_operator + { + public: + mult_operator() + : binary_operator(3) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + if (!(lhs.is_number() && rhs.is_number())) + { + return Json::null(); + } + else if (lhs.is_int64() && rhs.is_int64()) + { + return Json(((lhs.template as() * rhs.template as())), semantic_tag::none); + } + else if (lhs.is_uint64() && rhs.is_uint64()) + { + return Json((lhs.template as() * rhs.template as()), semantic_tag::none); + } + else + { + return Json((lhs.as_double() * rhs.as_double()), semantic_tag::none); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("multiply operator"); + return s; + } + }; + + template + class div_operator final : public binary_operator + { + public: + div_operator() + : binary_operator(3) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + //std::cout << "operator/ lhs: " << lhs << ", rhs: " << rhs << "\n"; + + if (!(lhs.is_number() && rhs.is_number())) + { + return Json::null(); + } + else if (lhs.is_int64() && rhs.is_int64()) + { + return Json(((lhs.template as() / rhs.template as())), semantic_tag::none); + } + else if (lhs.is_uint64() && rhs.is_uint64()) + { + return Json((lhs.template as() / rhs.template as()), semantic_tag::none); + } + else + { + return Json((lhs.as_double() / rhs.as_double()), semantic_tag::none); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("divide operator"); + return s; + } + }; + + template + class modulus_operator final : public binary_operator + { + public: + modulus_operator() + : binary_operator(3) + { + } + + Json evaluate(JsonReference lhs, JsonReference rhs, std::error_code&) const override + { + //std::cout << "operator/ lhs: " << lhs << ", rhs: " << rhs << "\n"; + + if (!(lhs.is_number() && rhs.is_number())) + { + return Json::null(); + } + else if (lhs.is_int64() && rhs.is_int64()) + { + return Json(((lhs.template as() % rhs.template as())), semantic_tag::none); + } + else if (lhs.is_uint64() && rhs.is_uint64()) + { + return Json((lhs.template as() % rhs.template as()), semantic_tag::none); + } + else + { + return Json(fmod(lhs.as_double(), rhs.as_double()), semantic_tag::none); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("modulus operator"); + return s; + } + }; + + // function_base + template + class function_base + { + jsoncons::optional arg_count_; + public: + using value_type = Json; + using parameter_type = parameter; + + function_base(jsoncons::optional arg_count) + : arg_count_(arg_count) + { + } + + virtual ~function_base() noexcept = default; + + jsoncons::optional arity() const + { + return arg_count_; + } + + virtual value_type evaluate(const std::vector& args, + std::error_code& ec) const = 0; + + virtual std::string to_string(int level = 0) const + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("function"); + return s; + } + }; + + template + class decorator_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + using string_view_type = typename Json::string_view_type; + using function_type = std::function, std::error_code& ec)>; + private: + function_type f_; + public: + decorator_function(jsoncons::optional arity, + const function_type& f) + : function_base(arity), f_(f) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + return f_(args, ec); + } + }; + + template + class contains_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + using string_view_type = typename Json::string_view_type; + + contains_function() + : function_base(2) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + auto arg1= args[1].value(); + + switch (arg0.type()) + { + case json_type::array_value: + for (auto& j : arg0.array_range()) + { + if (j == arg1) + { + return value_type(true, semantic_tag::none); + } + } + return value_type(false, semantic_tag::none); + case json_type::string_value: + { + if (!arg1.is_string()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + auto sv0 = arg0.template as(); + auto sv1 = arg1.template as(); + return sv0.find(sv1) != string_view_type::npos ? value_type(true, semantic_tag::none) : value_type(false, semantic_tag::none); + } + default: + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("contains function"); + return s; + } + }; + + template + class ends_with_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + using string_view_type = typename Json::string_view_type; + + ends_with_function() + : function_base(2) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + if (!arg0.is_string()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + + auto arg1= args[1].value(); + if (!arg1.is_string()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + + auto sv0 = arg0.template as(); + auto sv1 = arg1.template as(); + + if (sv1.length() <= sv0.length() && sv1 == sv0.substr(sv0.length() - sv1.length())) + { + return value_type(true, semantic_tag::none); + } + else + { + return value_type(false, semantic_tag::none); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("ends_with function"); + return s; + } + }; + + template + class starts_with_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + using string_view_type = typename Json::string_view_type; + + starts_with_function() + : function_base(2) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + if (!arg0.is_string()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + + auto arg1= args[1].value(); + if (!arg1.is_string()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + + auto sv0 = arg0.template as(); + auto sv1 = arg1.template as(); + + if (sv1.length() <= sv0.length() && sv1 == sv0.substr(0, sv1.length())) + { + return value_type(true, semantic_tag::none); + } + else + { + return value_type(false, semantic_tag::none); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("starts_with function"); + return s; + } + }; + + template + class sum_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + sum_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + if (!arg0.is_array()) + { + //std::cout << "arg: " << arg0 << "\n"; + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + //std::cout << "sum function arg: " << arg0 << "\n"; + + double sum = 0; + for (auto& j : arg0.array_range()) + { + if (!j.is_number()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + sum += j.template as(); + } + + return value_type(sum, semantic_tag::none); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("sum function"); + return s; + } + }; + +#if defined(JSONCONS_HAS_STD_REGEX) + + template + class tokenize_function : public function_base + { + using allocator_type = typename Json::allocator_type; + + allocator_type alloc_; + + public: + using value_type = Json; + using parameter_type = parameter; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = typename Json::string_view_type; + + tokenize_function(const allocator_type& alloc) + : function_base(2), alloc_(alloc) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + if (!args[0].value().is_string() || !args[1].value().is_string()) + { + //std::cout << "arg: " << arg0 << "\n"; + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + auto arg0 = args[0].value().template as(); + auto arg1 = args[1].value().template as(); + + auto s0 = string_type(arg0.begin(), arg0.end(), alloc_); + auto s1 = string_type(arg1.begin(), arg1.end(), alloc_); + + std::regex::flag_type options = std::regex_constants::ECMAScript; + std::basic_regex pieces_regex(s1, options); + + std::regex_token_iterator rit ( s0.begin(), s0.end(), pieces_regex, -1); + std::regex_token_iterator rend; + + value_type j(json_array_arg, semantic_tag::none, alloc_); + while (rit != rend) + { + auto s = rit->str(); + j.emplace_back(s.c_str(), semantic_tag::none); + ++rit; + } + return j; + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("tokenize function"); + return s; + } + }; + +#endif // defined(JSONCONS_HAS_STD_REGEX) + + template + class ceil_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + ceil_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + switch (arg0.type()) + { + case json_type::uint64_value: + case json_type::int64_value: + { + return value_type(arg0.template as(), semantic_tag::none); + } + case json_type::double_value: + { + return value_type(std::ceil(arg0.template as()), semantic_tag::none); + } + default: + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("ceil function"); + return s; + } + }; + + template + class floor_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + floor_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + switch (arg0.type()) + { + case json_type::uint64_value: + case json_type::int64_value: + { + return value_type(arg0.template as(), semantic_tag::none); + } + case json_type::double_value: + { + return value_type(std::floor(arg0.template as()), semantic_tag::none); + } + default: + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("floor function"); + return s; + } + }; + + template + class to_number_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + to_number_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + switch (arg0.type()) + { + case json_type::int64_value: + case json_type::uint64_value: + case json_type::double_value: + return arg0; + case json_type::string_value: + { + auto sv = arg0.as_string_view(); + uint64_t un{0}; + auto result1 = jsoncons::detail::to_integer(sv.data(), sv.length(), un); + if (result1) + { + return value_type(un, semantic_tag::none); + } + int64_t sn{0}; + auto result2 = jsoncons::detail::to_integer(sv.data(), sv.length(), sn); + if (result2) + { + return value_type(sn, semantic_tag::none); + } + jsoncons::detail::chars_to to_double; + try + { + auto s = arg0.as_string(); + double d = to_double(s.c_str(), s.length()); + return value_type(d, semantic_tag::none); + } + catch (const std::exception&) + { + return value_type::null(); + } + } + default: + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("to_number function"); + return s; + } + }; + + template + class prod_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + prod_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + if (!arg0.is_array() || arg0.empty()) + { + //std::cout << "arg: " << arg0 << "\n"; + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + double prod = 1; + for (auto& j : arg0.array_range()) + { + if (!j.is_number()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + prod *= j.template as(); + } + + return value_type(prod, semantic_tag::none); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("prod function"); + return s; + } + }; + + template + class avg_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + avg_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + if (!arg0.is_array()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + if (arg0.empty()) + { + return value_type::null(); + } + double sum = 0; + for (auto& j : arg0.array_range()) + { + if (!j.is_number()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + sum += j.template as(); + } + + return value_type(sum / static_cast(arg0.size()), semantic_tag::none); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("to_string function"); + return s; + } + }; + + template + class min_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + min_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + if (!arg0.is_array()) + { + //std::cout << "arg: " << arg0 << "\n"; + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + if (arg0.empty()) + { + return value_type::null(); + } + bool is_number = arg0.at(0).is_number(); + bool is_string = arg0.at(0).is_string(); + if (!is_number && !is_string) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + + std::size_t index = 0; + for (std::size_t i = 1; i < arg0.size(); ++i) + { + if (!(arg0.at(i).is_number() == is_number && arg0.at(i).is_string() == is_string)) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + if (arg0.at(i) < arg0.at(index)) + { + index = i; + } + } + + return arg0.at(index); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("min function"); + return s; + } + }; + + template + class max_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + max_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + if (!arg0.is_array()) + { + //std::cout << "arg: " << arg0 << "\n"; + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + if (arg0.empty()) + { + return value_type::null(); + } + + bool is_number = arg0.at(0).is_number(); + bool is_string = arg0.at(0).is_string(); + if (!is_number && !is_string) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + + std::size_t index = 0; + for (std::size_t i = 1; i < arg0.size(); ++i) + { + if (!(arg0.at(i).is_number() == is_number && arg0.at(i).is_string() == is_string)) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + if (arg0.at(i) > arg0.at(index)) + { + index = i; + } + } + + return arg0.at(index); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("max function"); + return s; + } + }; + + template + class abs_function : public function_base + { + public: + using value_type = Json; + using parameter_type = parameter; + + abs_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + switch (arg0.type()) + { + case json_type::uint64_value: + return arg0; + case json_type::int64_value: + { + return arg0.template as() >= 0 ? arg0 : value_type(std::abs(arg0.template as()), semantic_tag::none); + } + case json_type::double_value: + { + return arg0.template as() >= 0 ? arg0 : value_type(std::abs(arg0.template as()), semantic_tag::none); + } + default: + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("abs function"); + return s; + } + }; + + template + class length_function : public function_base + { + public: + using value_type = Json; + using string_view_type = typename Json::string_view_type; + using parameter_type = parameter; + + length_function() + : function_base(1) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0= args[0].value(); + //std::cout << "length function arg: " << arg0 << "\n"; + + switch (arg0.type()) + { + case json_type::object_value: + case json_type::array_value: + return value_type(arg0.size(), semantic_tag::none); + case json_type::string_value: + { + auto sv0 = arg0.template as(); + auto length = unicode_traits::count_codepoints(sv0.data(), sv0.size()); + return value_type(length, semantic_tag::none); + } + default: + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("length function"); + return s; + } + }; + + template + class keys_function : public function_base + { + using allocator_type = typename Json::allocator_type; + + allocator_type alloc_; + public: + using value_type = Json; + using parameter_type = parameter; + using string_view_type = typename Json::string_view_type; + + keys_function(const allocator_type& alloc) + : function_base(1), alloc_(alloc) + { + } + + value_type evaluate(const std::vector& args, + std::error_code& ec) const override + { + if (args.size() != *this->arity()) + { + ec = jsonpath_errc::invalid_arity; + return value_type::null(); + } + + auto arg0 = args[0].value(); + if (!arg0.is_object()) + { + ec = jsonpath_errc::invalid_type; + return value_type::null(); + } + + value_type result(json_array_arg, semantic_tag::none, alloc_); + result.reserve(args.size()); + + for (auto& item : arg0.object_range()) + { + auto s = item.key(); + result.emplace_back(s.c_str(), semantic_tag::none); + } + return result; + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("keys function"); + return s; + } + }; + + enum class jsonpath_token_kind + { + root_node, + current_node, + expression, + lparen, + rparen, + begin_union, + end_union, + begin_filter, + end_filter, + begin_expression, + end_index_expression, + end_argument_expression, + separator, + literal, + selector, + function, + end_function, + argument, + unary_operator, + binary_operator + }; + + inline + std::string to_string(jsonpath_token_kind kind) + { + switch (kind) + { + case jsonpath_token_kind::root_node: + return "root_node"; + case jsonpath_token_kind::current_node: + return "current_node"; + case jsonpath_token_kind::lparen: + return "lparen"; + case jsonpath_token_kind::rparen: + return "rparen"; + case jsonpath_token_kind::begin_union: + return "begin_union"; + case jsonpath_token_kind::end_union: + return "end_union"; + case jsonpath_token_kind::begin_filter: + return "begin_filter"; + case jsonpath_token_kind::end_filter: + return "end_filter"; + case jsonpath_token_kind::begin_expression: + return "begin_expression"; + case jsonpath_token_kind::end_index_expression: + return "end_index_expression"; + case jsonpath_token_kind::end_argument_expression: + return "end_argument_expression"; + case jsonpath_token_kind::separator: + return "separator"; + case jsonpath_token_kind::literal: + return "literal"; + case jsonpath_token_kind::selector: + return "selector"; + case jsonpath_token_kind::function: + return "function"; + case jsonpath_token_kind::end_function: + return "end_function"; + case jsonpath_token_kind::argument: + return "argument"; + case jsonpath_token_kind::unary_operator: + return "unary_operator"; + case jsonpath_token_kind::binary_operator: + return "binary_operator"; + default: + return ""; + } + } + + template + struct path_value_pair + { + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using value_type = Json; + using reference = JsonReference; + using value_pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + using path_node_type = basic_path_node; + using path_pointer = const path_node_type*; + + path_pointer path_ptr_; + value_pointer value_ptr_; + + path_value_pair(const path_node_type& path, reference value) noexcept + : path_ptr_(std::addressof(path)), value_ptr_(std::addressof(value)) + { + } + + path_value_pair(const path_value_pair&) = default; + path_value_pair(path_value_pair&& other) = default; + path_value_pair& operator=(const path_value_pair&) = default; + path_value_pair& operator=(path_value_pair&& other) = default; + + path_node_type path() const + { + return *path_ptr_; + } + + reference value() + { + return *value_ptr_; + } + }; + + template + struct path_value_pair_less + { + bool operator()(const path_value_pair& lhs, + const path_value_pair& rhs) const noexcept + { + return lhs.path() < rhs.path(); + } + }; + + template + struct path_value_pair_greater + { + bool operator()(const path_value_pair& lhs, + const path_value_pair& rhs) const noexcept + { + return rhs.path() < lhs.path(); + } + }; + + template + struct path_value_pair_equal + { + bool operator()(const path_value_pair& lhs, + const path_value_pair& rhs) const noexcept + { + return lhs.path() == rhs.path(); + } + }; + + template + struct path_component_value_pair + { + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using value_type = Json; + using reference = JsonReference; + using value_pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + using path_node_type = basic_path_node; + using path_pointer = const path_node_type*; + private: + const path_node_type* last_ptr_; + value_pointer value_ptr_; + public: + path_component_value_pair(const path_node_type& last, reference value) noexcept + : last_ptr_(std::addressof(last)), value_ptr_(std::addressof(value)) + { + } + + const path_node_type& last() const + { + return *last_ptr_; + } + + reference value() const + { + return *value_ptr_; + } + }; + + template + class node_receiver + { + public: + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using reference = JsonReference; + using path_node_type = basic_path_node; + + virtual ~node_receiver() noexcept = default; + + virtual void add(const path_node_type& base_path, reference value) = 0; + }; + + template + class path_value_receiver : public node_receiver + { + public: + using allocator_type = typename Json::allocator_type; + using reference = JsonReference; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using path_node_type = basic_path_node; + using path_value_pair_type = path_value_pair; + + allocator_type alloc_; + std::vector nodes; + + path_value_receiver(const allocator_type& alloc) + : alloc_(alloc) + { + } + + void add(const path_node_type& base_path, reference value) override + { + nodes.emplace_back(base_path, value); + } + }; + + template + class path_component_value_receiver : public node_receiver + { + public: + using reference = JsonReference; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using path_node_type = basic_path_node; + using path_component_value_pair_type = path_component_value_pair; + + std::vector nodes; + + void add(const path_node_type& base_path, reference value) override + { + nodes.emplace_back(base_path, value); + } + }; + + template + class dynamic_resources + { + using allocator_type = typename Json::allocator_type; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using reference = JsonReference; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + using path_node_type = basic_path_node; + using path_component_value_pair_type = path_component_value_pair; + + allocator_type alloc_; + std::vector> temp_json_values_; + std::vector> temp_node_values_; + std::unordered_map cache_; + string_type length_label_; + public: + dynamic_resources(const allocator_type& alloc = allocator_type()) + : alloc_(alloc), length_label_{JSONCONS_CSTRING_CONSTANT(char_type, "length"), alloc} + { + } + + allocator_type get_allocator() const + { + return alloc_; + } + + bool is_cached(std::size_t id) const + { + return cache_.find(id) != cache_.end(); + } + void add_to_cache(std::size_t id, reference val) + { + cache_.emplace(id, std::addressof(val)); + } + reference retrieve_from_cache(std::size_t id) + { + return *cache_[id]; + } + + reference null_value() + { + static Json a_null = Json(null_type(), semantic_tag::none); + return a_null; + } + + template + Json* create_json(Args&& ... args) + { + auto temp = jsoncons::make_unique(std::forward(args)...); + Json* ptr = temp.get(); + temp_json_values_.emplace_back(std::move(temp)); + return ptr; + } + + const string_type& length_label() const + { + return length_label_; + } + + template + const path_node_type* create_path_node(Args&& ... args) + { + auto temp = jsoncons::make_unique(std::forward(args)...); + path_node_type* ptr = temp.get(); + temp_node_values_.emplace_back(std::move(temp)); + return ptr; + } + }; + + template + struct node_less + { + bool operator()(const path_value_pair& a, const path_value_pair& b) const + { + return *(a.ptr) < *(b.ptr); + } + }; + + template + class jsonpath_selector + { + bool is_path_; + std::size_t precedence_level_; + + public: + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = jsoncons::basic_string_view>; + using value_type = Json; + using reference = JsonReference; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + using path_value_pair_type = path_value_pair; + using path_node_type = basic_path_node; + using node_receiver_type = node_receiver; + using selector_type = jsonpath_selector; + + jsonpath_selector(bool is_path, + std::size_t precedence_level = 0) + : is_path_(is_path), + precedence_level_(precedence_level) + { + } + + virtual ~jsonpath_selector() noexcept = default; + + bool is_path() const + { + return is_path_; + } + + std::size_t precedence_level() const + { + return precedence_level_; + } + + bool is_right_associative() const + { + return true; + } + + virtual void select(dynamic_resources& resources, + reference root, + const path_node_type& base_path, + reference val, + node_receiver_type& receiver, + result_options options) const = 0; + + virtual reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& base_path, + reference current, + result_options options, + std::error_code& ec) const = 0; + + virtual void append_selector(jsonpath_selector*) + { + } + + virtual std::string to_string(int = 0) const + { + return std::string(); + } + }; + + template + struct static_resources + { + using allocator_type = typename Json::allocator_type; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using value_type = Json; + using reference = JsonReference; + using function_base_type = function_base; + using selector_type = jsonpath_selector; + + struct MyHash + { + std::uintmax_t operator()(string_type const& s) const noexcept + { + const int p = 31; + const int m = static_cast(1e9) + 9; + std::uintmax_t hash_value = 0; + std::uintmax_t p_pow = 1; + for (char_type c : s) { + hash_value = (hash_value + (c - 'a' + 1) * p_pow) % m; + p_pow = (p_pow * p) % m; + } + return hash_value; + } + }; + + allocator_type alloc_; + std::vector> selectors_; + std::vector> temp_json_values_; + std::vector>> unary_operators_; + + std::unordered_map,MyHash> functions_; + std::unordered_map,MyHash> custom_functions_; + + static_resources(const allocator_type& alloc = allocator_type()) + : alloc_(alloc) + { + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "abs"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{ JSONCONS_CSTRING_CONSTANT(char_type, "contains"), alloc_ }, + jsoncons::make_unique>()); + functions_.emplace(string_type{ JSONCONS_CSTRING_CONSTANT(char_type, "starts_with"), alloc_ }, + jsoncons::make_unique>()); + functions_.emplace(string_type{ JSONCONS_CSTRING_CONSTANT(char_type, "ends_with"), alloc_ }, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "ceil"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "floor"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{ JSONCONS_CSTRING_CONSTANT(char_type, "to_number"), alloc_ }, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "sum"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "prod"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "avg"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "min"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "max"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "length"), alloc_}, + jsoncons::make_unique>()); + functions_.emplace(string_type{ JSONCONS_CSTRING_CONSTANT(char_type, "keys"), alloc_ }, + jsoncons::make_unique>(alloc_)); +#if defined(JSONCONS_HAS_STD_REGEX) + functions_.emplace(string_type{ JSONCONS_CSTRING_CONSTANT(char_type, "tokenize"), alloc_ }, + jsoncons::make_unique>(alloc_)); +#endif + functions_.emplace(string_type{JSONCONS_CSTRING_CONSTANT(char_type, "count"), alloc_}, + jsoncons::make_unique>()); + } + + static_resources(const custom_functions& functions, + const allocator_type& alloc = allocator_type()) + : static_resources(alloc) + { + for (const auto& item : functions) + { + custom_functions_.emplace(item.name(), + jsoncons::make_unique>(item.arity(),item.function())); + } + } + + static_resources(const static_resources&) = delete; + + static_resources operator=(const static_resources&) = delete; + + //static_resources(static_resources&&) = delete; + + static_resources operator=(static_resources&&) = delete; + + static_resources(static_resources&& other) noexcept + : alloc_(std::move(other.alloc_)), + selectors_(std::move(other.selectors_)), + temp_json_values_(std::move(other.temp_json_values_)), + unary_operators_(std::move(other.unary_operators_)), + functions_(std::move(other.functions_)), + custom_functions_(std::move(other.custom_functions_)) + { + } + + const function_base_type* get_function(const string_type& name, std::error_code& ec) const + { + auto it = functions_.find(name); + if (it == functions_.end()) + { + auto it2 = custom_functions_.find(name); + if (it2 == custom_functions_.end()) + { + ec = jsonpath_errc::unknown_function; + return nullptr; + } + else + { + return it2->second.get(); + } + } + else + { + return it->second.get(); + } + } + + const unary_operator* get_unary_not() const + { + static unary_not_operator oper; + return &oper; + } + + const unary_operator* get_unary_minus() const + { + static unary_minus_operator oper; + return &oper; + } + + const unary_operator* get_regex_operator(std::basic_regex&& pattern) + { + unary_operators_.push_back(jsoncons::make_unique>(std::move(pattern))); + return unary_operators_.back().get(); + } + + const binary_operator* get_or_operator() const + { + static or_operator oper; + + return &oper; + } + + const binary_operator* get_and_operator() const + { + static and_operator oper; + + return &oper; + } + + const binary_operator* get_eq_operator() const + { + static eq_operator oper; + return &oper; + } + + const binary_operator* get_ne_operator() const + { + static ne_operator oper; + return &oper; + } + + const binary_operator* get_lt_operator() const + { + static lt_operator oper; + return &oper; + } + + const binary_operator* get_lte_operator() const + { + static lte_operator oper; + return &oper; + } + + const binary_operator* get_gt_operator() const + { + static gt_operator oper; + return &oper; + } + + const binary_operator* get_gte_operator() const + { + static gte_operator oper; + return &oper; + } + + const binary_operator* get_plus_operator() const + { + static plus_operator oper; + return &oper; + } + + const binary_operator* get_minus_operator() const + { + static minus_operator oper; + return &oper; + } + + const binary_operator* get_mult_operator() const + { + static mult_operator oper; + return &oper; + } + + const binary_operator* get_div_operator() const + { + static div_operator oper; + return &oper; + } + + const binary_operator* get_modulus_operator() const + { + static modulus_operator oper; + return &oper; + } + + template + selector_type* new_selector(T&& val) + { + selectors_.emplace_back(jsoncons::make_unique(std::forward(val))); + return selectors_.back().get(); + } + + template + Json* create_json(Args&& ... args) + { + auto temp = jsoncons::make_unique(std::forward(args)...); + Json* ptr = temp.get(); + temp_json_values_.emplace_back(std::move(temp)); + return ptr; + } + }; + + template + class expression_base + { + public: + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = jsoncons::basic_string_view>; + using value_type = Json; + using reference = JsonReference; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + using path_value_pair_type = path_value_pair; + using path_node_type = basic_path_node; + + virtual ~expression_base() noexcept = default; + + virtual value_type evaluate(dynamic_resources& resources, + reference root, + //const path_node_type& path, + reference val, + result_options options, + std::error_code& ec) const = 0; + + virtual std::string to_string(int level = 0) const = 0; + }; + + template + class token + { + public: + using selector_type = jsonpath_selector; + using expression_base_type = expression_base; + + jsonpath_token_kind token_kind_; + + union + { + char dummy_; + selector_type* selector_; + std::unique_ptr expression_; + const unary_operator* unary_operator_; + const binary_operator* binary_operator_; + const function_base* function_; + Json value_; + }; + public: + + token(const unary_operator* expr) noexcept + : token_kind_(jsonpath_token_kind::unary_operator), + unary_operator_(expr) + { + } + + token(const binary_operator* expr) noexcept + : token_kind_(jsonpath_token_kind::binary_operator), + binary_operator_(expr) + { + } + + token(current_node_arg_t) noexcept + : token_kind_(jsonpath_token_kind::current_node), dummy_{} + { + } + + token(root_node_arg_t) noexcept + : token_kind_(jsonpath_token_kind::root_node), dummy_{} + { + } + + token(end_function_arg_t) noexcept + : token_kind_(jsonpath_token_kind::end_function), dummy_{} + { + } + + token(separator_arg_t) noexcept + : token_kind_(jsonpath_token_kind::separator), dummy_{} + { + } + + token(lparen_arg_t) noexcept + : token_kind_(jsonpath_token_kind::lparen), dummy_{} + { + } + + token(rparen_arg_t) noexcept + : token_kind_(jsonpath_token_kind::rparen), dummy_{} + { + } + + token(begin_union_arg_t) noexcept + : token_kind_(jsonpath_token_kind::begin_union), dummy_{} + { + } + + token(end_union_arg_t) noexcept + : token_kind_(jsonpath_token_kind::end_union), dummy_{} + { + } + + token(begin_filter_arg_t) noexcept + : token_kind_(jsonpath_token_kind::begin_filter), dummy_{} + { + } + + token(end_filter_arg_t) noexcept + : token_kind_(jsonpath_token_kind::end_filter), dummy_{} + { + } + + token(begin_expression_arg_t) noexcept + : token_kind_(jsonpath_token_kind::begin_expression), dummy_{} + { + } + + token(end_index_expression_arg_t) noexcept + : token_kind_(jsonpath_token_kind::end_index_expression), dummy_{} + { + } + + token(end_argument_expression_arg_t) noexcept + : token_kind_(jsonpath_token_kind::end_argument_expression), dummy_{} + { + } + + token(selector_type* selector) + : token_kind_(jsonpath_token_kind::selector), selector_(selector) + { + } + + token(std::unique_ptr&& expr) + : token_kind_(jsonpath_token_kind::expression) + { + new (&expression_) std::unique_ptr(std::move(expr)); + } + + token(const function_base* function) noexcept + : token_kind_(jsonpath_token_kind::function), + function_(function) + { + } + + token(argument_arg_t) noexcept + : token_kind_(jsonpath_token_kind::argument), dummy_{} + { + } + + token(literal_arg_t, Json&& value) noexcept + : token_kind_(jsonpath_token_kind::literal), value_(std::move(value)) + { + } + + token(token&& other) noexcept + { + construct(std::move(other)); + } + + const Json& get_value(const_reference_arg_t, dynamic_resources&) const + { + return value_; + } + + Json& get_value(reference_arg_t, dynamic_resources& resources) const + { + return *resources.create_json(value_); + } + +#if defined(__GNUC__) && JSONCONS_GCC_AVAILABLE(12,0,0) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + token& operator=(token&& other) + { + + if (&other != this) + { + if (token_kind_ == other.token_kind_) + { + switch (token_kind_) + { + case jsonpath_token_kind::selector: + selector_ = other.selector_; + break; + case jsonpath_token_kind::expression: + expression_ = std::move(other.expression_); + break; + case jsonpath_token_kind::unary_operator: + unary_operator_ = other.unary_operator_; + break; + case jsonpath_token_kind::binary_operator: + binary_operator_ = other.binary_operator_; + break; + case jsonpath_token_kind::function: + function_ = other.function_; + break; + case jsonpath_token_kind::literal: + value_ = std::move(other.value_); + break; + default: + break; + } + } + else + { + destroy(); + construct(std::move(other)); + } + } + return *this; + } +#if defined(__GNUC__) && JSONCONS_GCC_AVAILABLE(12,0,0) +# pragma GCC diagnostic pop +#endif + + ~token() noexcept + { + destroy(); + } + + jsonpath_token_kind token_kind() const + { + return token_kind_; + } + + bool is_lparen() const + { + return token_kind_ == jsonpath_token_kind::lparen; + } + + bool is_rparen() const + { + return token_kind_ == jsonpath_token_kind::rparen; + } + + bool is_current_node() const + { + return token_kind_ == jsonpath_token_kind::current_node; + } + + bool is_path() const + { + return token_kind_ == jsonpath_token_kind::selector && selector_->is_path(); + } + + bool is_operator() const + { + return token_kind_ == jsonpath_token_kind::unary_operator || + token_kind_ == jsonpath_token_kind::binary_operator; + } + + std::size_t precedence_level() const + { + switch(token_kind_) + { + case jsonpath_token_kind::selector: + return selector_->precedence_level(); + case jsonpath_token_kind::unary_operator: + return unary_operator_->precedence_level(); + case jsonpath_token_kind::binary_operator: + return binary_operator_->precedence_level(); + default: + return 0; + } + } + + jsoncons::optional arity() const + { + return token_kind_ == jsonpath_token_kind::function ? function_->arity() : jsoncons::optional(); + } + + bool is_right_associative() const + { + switch(token_kind_) + { + case jsonpath_token_kind::selector: + return selector_->is_right_associative(); + case jsonpath_token_kind::unary_operator: + return unary_operator_->is_right_associative(); + case jsonpath_token_kind::binary_operator: + return binary_operator_->is_right_associative(); + default: + return false; + } + } + +#if defined(__GNUC__) && JSONCONS_GCC_AVAILABLE(12,0,0) +# pragma GCC diagnostic push +# pragma GCC diagnostic ignored "-Wmaybe-uninitialized" +#endif + void construct(token&& other) + { + token_kind_ = other.token_kind_; + switch (token_kind_) + { + case jsonpath_token_kind::selector: + selector_ = other.selector_; + break; + case jsonpath_token_kind::expression: + new (&expression_) std::unique_ptr(std::move(other.expression_)); + break; + case jsonpath_token_kind::unary_operator: + unary_operator_ = other.unary_operator_; + break; + case jsonpath_token_kind::binary_operator: + binary_operator_ = other.binary_operator_; + break; + case jsonpath_token_kind::function: + function_ = other.function_; + break; + case jsonpath_token_kind::literal: + new (&value_) Json(std::move(other.value_)); + break; + default: + break; + } + } +#if defined(__GNUC__) && JSONCONS_GCC_AVAILABLE(12,0,0) +# pragma GCC diagnostic pop +#endif + + void destroy() noexcept + { + switch(token_kind_) + { + case jsonpath_token_kind::expression: + expression_.~unique_ptr(); + break; + case jsonpath_token_kind::literal: + value_.~Json(); + break; + default: + break; + } + } + + std::string to_string(int level = 0) const + { + std::string s; + switch (token_kind_) + { + case jsonpath_token_kind::root_node: + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("root node"); + break; + case jsonpath_token_kind::current_node: + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("current node"); + break; + case jsonpath_token_kind::argument: + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("argument"); + break; + case jsonpath_token_kind::selector: + s.append(selector_->to_string(level)); + break; + case jsonpath_token_kind::expression: + s.append(expression_->to_string(level)); + break; + case jsonpath_token_kind::literal: + { + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + auto sbuf = value_.to_string(); + unicode_traits::convert(sbuf.data(), sbuf.size(), s); + break; + } + case jsonpath_token_kind::binary_operator: + s.append(binary_operator_->to_string(level)); + break; + case jsonpath_token_kind::function: + s.append(function_->to_string(level)); + break; + default: + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("token kind: "); + s.append(jsoncons::jsonpath::detail::to_string(token_kind_)); + break; + } + //s.append("\n"); + return s; + } + }; + + template + class callback_receiver : public node_receiver + { + public: + using allocator_type = typename Json::allocator_type; + using reference = JsonReference; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using path_node_type = basic_path_node; + private: + allocator_type alloc_; + Callback& callback_; + public: + + callback_receiver(Callback& callback, const allocator_type& alloc) + : alloc_(alloc), callback_(callback) + { + } + + void add(const path_node_type& base_path, + reference value) override + { + callback_(base_path, value); + } + }; + + template + class path_expression + { + public: + using allocator_type = typename Json::allocator_type; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = typename Json::string_view_type; + using path_value_pair_type = path_value_pair; + using path_value_pair_less_type = path_value_pair_less; + using path_value_pair_greater_type = path_value_pair_greater; + using path_value_pair_equal_type = path_value_pair_equal; + using value_type = Json; + using reference = typename path_value_pair_type::reference; + using pointer = typename path_value_pair_type::value_pointer; + using token_type = token; + using reference_arg_type = typename std::conditional::type>::value, + const_reference_arg_t,reference_arg_t>::type; + using path_node_type = basic_path_node; + using selector_type = jsonpath_selector; + private: + allocator_type alloc_; + selector_type* selector_; + result_options required_options_; + public: + + path_expression(selector_type* selector, bool paths_required, const allocator_type& alloc) + : alloc_(alloc), selector_(selector), required_options_() + { + if (paths_required) + { + required_options_ |= result_options::path; + } + } + + path_expression(const allocator_type& alloc) + : alloc_(alloc), selector_(nullptr), required_options_() + { + } + + path_expression(path_expression&& expr) = default; + + path_expression& operator=(path_expression&& expr) = default; + + Json evaluate(dynamic_resources& resources, + reference root, + const path_node_type& path, + reference instance, + result_options options) const + { + Json result(json_array_arg, semantic_tag::none, alloc_); + + if ((options & result_options::path) == result_options::path) + { + auto callback = [&result](const path_node_type& pathp, reference) + { + result.emplace_back(to_basic_string(pathp)); + }; + evaluate(resources, root, path, instance, callback, options); + } + else + { + auto callback = [&result](const path_node_type&, reference val) + { + result.push_back(val); + }; + evaluate(resources, root, path, instance, callback, options); + } + + return result; + } + + template + typename std::enable_if::value,void>::type + evaluate(dynamic_resources& resources, + reference root, + const path_node_type& path, + reference current, + Callback callback, + result_options options) const + { + std::error_code ec; + + options |= required_options_; + + const result_options require_more = result_options::nodups | result_options::sort | result_options::sort_descending; + + if (selector_ != nullptr && (options & require_more) != result_options()) + { + path_value_receiver receiver{alloc_}; + selector_->select(resources, root, path, current, receiver, options); + + if (receiver.nodes.size() > 1) + { + if ((options & result_options::sort_descending) == result_options::sort_descending) + { + std::sort(receiver.nodes.begin(), receiver.nodes.end(), path_value_pair_greater_type()); + } + else if ((options & result_options::sort) == result_options::sort) + { + std::sort(receiver.nodes.begin(), receiver.nodes.end(), path_value_pair_less_type()); + } + } + + if (receiver.nodes.size() > 1 && (options & result_options::nodups) == result_options::nodups) + { + if ((options & result_options::sort_descending) == result_options::sort_descending) + { + auto last = std::unique(receiver.nodes.rbegin(),receiver.nodes.rend(),path_value_pair_equal_type()); + receiver.nodes.erase(receiver.nodes.begin(), last.base()); + for (auto& node : receiver.nodes) + { + callback(node.path(), node.value()); + } + } + else if ((options & result_options::sort) == result_options::sort) + { + auto last = std::unique(receiver.nodes.begin(),receiver.nodes.end(),path_value_pair_equal_type()); + receiver.nodes.erase(last,receiver.nodes.end()); + for (auto& node : receiver.nodes) + { + callback(node.path(), node.value()); + } + } + else + { + std::vector index(receiver.nodes); + std::sort(index.begin(), index.end(), path_value_pair_less_type()); + auto last = std::unique(index.begin(),index.end(),path_value_pair_equal_type()); + index.erase(last,index.end()); + + std::vector temp2; + temp2.reserve(index.size()); + for (auto&& node : receiver.nodes) + { + auto it = std::lower_bound(index.begin(),index.end(),node, path_value_pair_less_type()); + if (it != index.end() && it->path() == node.path()) + { + temp2.emplace_back(std::move(node)); + index.erase(it); + } + } + for (auto& node : temp2) + { + callback(node.path(), node.value()); + } + } + } + else + { + for (auto& node : receiver.nodes) + { + callback(node.path(), node.value()); + } + } + } + else + { + callback_receiver receiver(callback, alloc_); + selector_->select(resources, root, path, current, receiver, options); + } + } + + std::string to_string(int level) const + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("expression "); + if (selector_ != nullptr) + { + s.append(selector_->to_string(level+1)); + } + + return s; + + } + }; + + template + class expression : public expression_base + { + public: + using path_value_pair_type = path_value_pair; + using value_type = Json; + using reference = typename path_value_pair_type::reference; + using pointer = typename path_value_pair_type::value_pointer; + using const_pointer = const value_type*; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = typename Json::string_view_type; + using path_value_pair_less_type = path_value_pair_less; + using path_value_pair_greater_type = path_value_pair_greater; + using path_value_pair_equal_type = path_value_pair_equal; + using parameter_type = parameter; + using token_type = token; + using reference_arg_type = typename std::conditional::type>::value, + const_reference_arg_t,reference_arg_t>::type; + using path_node_type = basic_path_node; + using stack_item_type = value_or_pointer; + private: + std::vector token_list_; + public: + + expression() + { + } + + expression(expression&& expr) + : token_list_(std::move(expr.token_list_)) + { + } + + expression(std::vector&& token_stack) + : token_list_(std::move(token_stack)) + { + } + + expression& operator=(expression&& expr) = default; + + value_type evaluate(dynamic_resources& resources, + reference root, + reference current, + result_options options, + std::error_code& ec) const override + { + std::vector stack; + std::vector arg_stack; + + //std::cout << "EVALUATE TOKENS\n"; + //for (auto& tok : token_list_) + //{ + // std::cout << tok.to_string() << "\n"; + //} + //std::cout << "\n"; + + if (!token_list_.empty()) + { + for (auto& tok : token_list_) + { + //std::cout << "Token: " << tok.to_string() << "\n"; + switch (tok.token_kind()) + { + case jsonpath_token_kind::literal: + { + stack.emplace_back(std::addressof(tok.get_value(reference_arg_type(), resources))); + break; + } + case jsonpath_token_kind::unary_operator: + { + JSONCONS_ASSERT(stack.size() >= 1); + auto item = std::move(stack.back()); + stack.pop_back(); + + auto val = tok.unary_operator_->evaluate(item.value(), ec); + stack.emplace_back(std::move(val)); + break; + } + case jsonpath_token_kind::binary_operator: + { + //std::cout << "binary operator: " << stack.size() << "\n"; + JSONCONS_ASSERT(stack.size() >= 2); + auto rhs = std::move(stack.back()); + //std::cout << "rhs: " << *rhs << "\n"; + stack.pop_back(); + auto lhs = std::move(stack.back()); + //std::cout << "lhs: " << *lhs << "\n"; + stack.pop_back(); + + auto val = tok.binary_operator_->evaluate(lhs.value(), rhs.value(), ec); + //std::cout << "Evaluate binary expression: " << r << "\n"; + stack.emplace_back(std::move(val)); + break; + } + case jsonpath_token_kind::root_node: + //std::cout << "root: " << root << "\n"; + stack.emplace_back(std::addressof(root)); + break; + case jsonpath_token_kind::current_node: + //std::cout << "current: " << current << "\n"; + stack.emplace_back(std::addressof(current)); + break; + case jsonpath_token_kind::argument: + JSONCONS_ASSERT(!stack.empty()); + //std::cout << "argument stack items " << stack.size() << "\n"; + //for (auto& item : stack) + //{ + // std::cout << *item.to_pointer(resources) << "\n"; + //} + //std::cout << "\n"; + arg_stack.emplace_back(std::move(stack.back())); + //for (auto& item : arg_stack) + //{ + // std::cout << *item << "\n"; + //} + //std::cout << "\n"; + stack.pop_back(); + break; + case jsonpath_token_kind::function: + { + if (tok.function_->arity() && *(tok.function_->arity()) != arg_stack.size()) + { + ec = jsonpath_errc::invalid_arity; + return Json::null(); + } + //std::cout << "function arg stack:\n"; + //for (auto& item : arg_stack) + //{ + // std::cout << *item << "\n"; + //} + //std::cout << "\n"; + + value_type val = tok.function_->evaluate(arg_stack, ec); + if (ec) + { + return Json::null(); + } + //std::cout << "function result: " << val << "\n"; + arg_stack.clear(); + stack.emplace_back(std::move(val)); + break; + } + case jsonpath_token_kind::expression: + { + value_type val = tok.expression_->evaluate(resources, root, current, options, ec); + stack.emplace_back(std::move(val)); + break; + } + case jsonpath_token_kind::selector: + { + JSONCONS_ASSERT(!stack.empty()); + auto item = std::move(stack.back()); + //for (auto& item : stack) + //{ + //std::cout << "selector stack input:\n"; + //switch (item.tag) + //{ + // case node_set_tag::single: + // std::cout << "single: " << *(item.node.ptr) << "\n"; + // break; + // case node_set_tag::multi: + // for (auto& node : stack.back().ptr().nodes) + // { + // std::cout << "multi: " << *node.ptr << "\n"; + // } + // break; + // default: + // break; + //} + //std::cout << "\n"; + //} + //std::cout << "selector item: " << *ptr << "\n"; + + reference val = tok.selector_->evaluate(resources, root, path_node_type{}, item.value(), options, ec); + + stack.pop_back(); + stack.emplace_back(stack_item_type(std::addressof(val))); + break; + } + default: + break; + } + } + } + + //if (stack.size() != 1) + //{ + // std::cout << "Stack size: " << stack.size() << "\n"; + //} + return stack.empty() ? Json::null() : stack.back().value(); + } + + std::string to_string(int level) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("expression "); + for (const auto& item : token_list_) + { + s.append(item.to_string(level+1)); + } + + return s; + + } + private: + }; + +} // namespace detail +} // namespace jsonpath +} // namespace jsoncons + +#endif // JSONCONS_JSONPATH_JSONPATH_EXPRESSION_HPP diff --git a/third_party/jsoncons_ext/jsonpath/flatten.hpp b/third_party/jsoncons_ext/jsonpath/flatten.hpp new file mode 100644 index 0000000000..0139069bfd --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/flatten.hpp @@ -0,0 +1,377 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_FLATTEN_HPP +#define JSONCONS_JSONPATH_FLATTEN_HPP + +#include +#include +#include +#include // std::is_const +#include // std::numeric_limits +#include // std::move +#include // std::copy +#include // std::back_inserter +#include +#include + +namespace jsoncons { namespace jsonpath { + + template + void flatten_(const typename Json::string_type& parent_key, + const Json& parent_value, + Json& result) + { + using char_type = typename Json::char_type; + using string_type = std::basic_string; + + switch (parent_value.type()) + { + case json_type::array_value: + { + if (parent_value.empty()) + { + result.try_emplace(parent_key, parent_value); + } + else + { + for (std::size_t i = 0; i < parent_value.size(); ++i) + { + string_type key(parent_key); + key.push_back('['); + jsoncons::detail::from_integer(i,key); + key.push_back(']'); + flatten_(key, parent_value.at(i), result); + } + } + break; + } + + case json_type::object_value: + { + if (parent_value.empty()) + { + result.try_emplace(parent_key, Json()); + } + else + { + for (const auto& item : parent_value.object_range()) + { + string_type key(parent_key); + key.push_back('['); + key.push_back('\''); + escape_string(item.key().data(), item.key().length(), key); + key.push_back('\''); + key.push_back(']'); + flatten_(key, item.value(), result); + } + } + break; + } + + default: + { + result[parent_key] = parent_value; + break; + } + } + } + + template + Json flatten(const Json& value) + { + Json result; + typename Json::string_type parent_key = {'$'}; + flatten_(parent_key, value, result); + return result; + } + + enum class unflatten_state + { + start, + expect_lbracket, + lbracket, + single_quoted_name_state, + double_quoted_name_state, + index_state, + expect_rbracket, + double_quoted_string_escape_char, + single_quoted_string_escape_char + }; + + template + Json unflatten(const Json& value) + { + using char_type = typename Json::char_type; + using string_type = std::basic_string; + + if (JSONCONS_UNLIKELY(!value.is_object())) + { + JSONCONS_THROW(jsonpath_error(jsonpath_errc::argument_to_unflatten_invalid)); + } + + Json result; + + for (const auto& item : value.object_range()) + { + Json* part = &result; + string_type buffer; + unflatten_state state = unflatten_state::start; + + auto it = item.key().begin(); + auto last = item.key().end(); + + for (; it != last; ++it) + { + switch (state) + { + case unflatten_state::start: + { + switch (*it) + { + case '$': + state = unflatten_state::expect_lbracket; + break; + default: + break; + } + break; + } + case unflatten_state::expect_lbracket: + { + switch (*it) + { + case '[': + state = unflatten_state::lbracket; + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + case unflatten_state::lbracket: + { + switch (*it) + { + case '\'': + state = unflatten_state::single_quoted_name_state; + break; + case '\"': + state = unflatten_state::double_quoted_name_state; + break; + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*it); + state = unflatten_state::index_state; + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + case unflatten_state::single_quoted_name_state: + { + switch (*it) + { + case '\'': + if (it != last-2) + { + auto res = part->try_emplace(buffer,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(buffer,item.value()); + part = &(res.first->value()); + } + buffer.clear(); + state = unflatten_state::expect_rbracket; + break; + case '\\': + state = unflatten_state::single_quoted_string_escape_char; + break; + default: + buffer.push_back(*it); + break; + } + break; + } + case unflatten_state::double_quoted_name_state: + { + switch (*it) + { + case '\"': + if (it != last-2) + { + auto res = part->try_emplace(buffer,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(buffer,item.value()); + part = &(res.first->value()); + } + buffer.clear(); + state = unflatten_state::expect_rbracket; + break; + case '\\': + state = unflatten_state::double_quoted_string_escape_char; + break; + default: + buffer.push_back(*it); + break; + } + break; + } + case unflatten_state::double_quoted_string_escape_char: + switch (*it) + { + case '\"': + buffer.push_back('\"'); + state = unflatten_state::double_quoted_name_state; + break; + case '\\': + buffer.push_back('\\'); + state = unflatten_state::double_quoted_name_state; + break; + case '/': + buffer.push_back('/'); + state = unflatten_state::double_quoted_name_state; + break; + case 'b': + buffer.push_back('\b'); + state = unflatten_state::double_quoted_name_state; + break; + case 'f': + buffer.push_back('\f'); + state = unflatten_state::double_quoted_name_state; + break; + case 'n': + buffer.push_back('\n'); + state = unflatten_state::double_quoted_name_state; + break; + case 'r': + buffer.push_back('\r'); + state = unflatten_state::double_quoted_name_state; + break; + case 't': + buffer.push_back('\t'); + state = unflatten_state::double_quoted_name_state; + break; + default: + break; + } + break; + case unflatten_state::single_quoted_string_escape_char: + switch (*it) + { + case '\'': + buffer.push_back('\''); + state = unflatten_state::single_quoted_name_state; + break; + case '\\': + buffer.push_back('\\'); + state = unflatten_state::double_quoted_name_state; + break; + case '/': + buffer.push_back('/'); + state = unflatten_state::double_quoted_name_state; + break; + case 'b': + buffer.push_back('\b'); + state = unflatten_state::double_quoted_name_state; + break; + case 'f': + buffer.push_back('\f'); + state = unflatten_state::double_quoted_name_state; + break; + case 'n': + buffer.push_back('\n'); + state = unflatten_state::double_quoted_name_state; + break; + case 'r': + buffer.push_back('\r'); + state = unflatten_state::double_quoted_name_state; + break; + case 't': + buffer.push_back('\t'); + state = unflatten_state::double_quoted_name_state; + break; + default: + break; + } + break; + case unflatten_state::index_state: + { + switch (*it) + { + case ']': + { + std::size_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (r) + { + if (!part->is_array()) + { + *part = Json(json_array_arg, semantic_tag::none, value.get_allocator()); + } + if (it != last-1) + { + if (n+1 > part->size()) + { + Json& ref = part->emplace_back(); + part = std::addressof(ref); + } + else + { + part = &part->at(n); + } + } + else + { + Json& ref = part->emplace_back(item.value()); + part = std::addressof(ref); + } + } + buffer.clear(); + state = unflatten_state::expect_lbracket; + break; + } + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*it); + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + case unflatten_state::expect_rbracket: + { + switch (*it) + { + case ']': + state = unflatten_state::expect_lbracket; + break; + default: + JSONCONS_THROW(jsonpath_error(jsonpath_errc::invalid_flattened_key)); + break; + } + break; + } + default: + JSONCONS_UNREACHABLE(); + break; + } + } + } + + return result; + } +}} + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/json_location.hpp b/third_party/jsoncons_ext/jsonpath/json_location.hpp new file mode 100644 index 0000000000..8412239575 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/json_location.hpp @@ -0,0 +1,956 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSON_LOCATION_HPP +#define JSONCONS_JSONPATH_JSON_LOCATION_HPP + +#include +#include +#include +#include // std::is_const +#include // std::numeric_limits +#include // std::move +#include // std::reverse +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonpath { + + template + class basic_path_element + { + public: + using char_type = CharT; + using allocator_type = Allocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using string_type = std::basic_string,char_allocator_type>; + private: + bool has_name_; + string_type name_; + std::size_t index_; + + public: + basic_path_element(const char_type* name, std::size_t length, + const Allocator& alloc = Allocator()) + : has_name_(true), name_(name, length, alloc), index_(0) + { + } + + explicit basic_path_element(const string_type& name) + : has_name_(true), name_(name), index_(0) + { + } + + explicit basic_path_element(string_type&& name) + : has_name_(true), name_(std::move(name)), index_(0) + { + } + + basic_path_element(std::size_t index, + const Allocator& alloc = Allocator()) + : has_name_(false), name_(alloc), index_(index) + { + } + + basic_path_element(const basic_path_element& other) = default; + + basic_path_element& operator=(const basic_path_element& other) = default; + + bool has_name() const + { + return has_name_; + } + + bool has_index() const + { + return !has_name_; + } + + const string_type& name() const + { + return name_; + } + + std::size_t index() const + { + return index_; + } + + int compare(const basic_path_element& other) const + { + int diff = 0; + if (has_name_ != other.has_name_) + { + diff = static_cast(has_name_) - static_cast(other.has_name_); + } + else + { + if (has_name_) + { + diff = name_.compare(other.name_); + } + else + { + diff = index_ < other.index_ ? -1 : index_ > other.index_ ? 1 : 0; + } + } + return diff; + } + }; + + // parser + namespace detail { + + enum class json_location_state + { + start, + relative_location, + single_quoted_string, + double_quoted_string, + unquoted_string, + selector, + digit, + expect_rbracket, + quoted_string_escape_char + }; + + enum class selector_separator_kind{bracket,dot}; + + template + class json_location_parser + { + public: + using allocator_type = Allocator; + using char_type = CharT; + using string_type = std::basic_string; + using string_view_type = jsoncons::basic_string_view; + using path_element_type = basic_path_element; + using path_element_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using path_type = std::vector; + + private: + + allocator_type alloc_; + std::size_t line_; + std::size_t column_; + const char_type* end_input_; + const char_type* p_; + + public: + json_location_parser(const allocator_type& alloc = allocator_type()) + : alloc_(alloc), line_(1), column_(1), + end_input_(nullptr), + p_(nullptr) + { + } + + json_location_parser(std::size_t line, std::size_t column, + const allocator_type& alloc = allocator_type()) + : alloc_(alloc), line_(line), column_(column), + end_input_(nullptr), + p_(nullptr) + { + } + + std::size_t line() const + { + return line_; + } + + std::size_t column() const + { + return column_; + } + + path_type parse(const string_view_type& path) + { + std::error_code ec; + auto result = parse(path, ec); + if (ec) + { + JSONCONS_THROW(jsonpath_error(ec, line_, column_)); + } + return result; + } + + path_type parse(const string_view_type& path, std::error_code& ec) + { + std::vector elements; + + string_type buffer(alloc_); + + end_input_ = path.data() + path.length(); + p_ = path.data(); + + + selector_separator_kind separator_kind = selector_separator_kind::bracket; + + json_location_state state = json_location_state::start; + + while (p_ < end_input_) + { + switch (state) + { + case json_location_state::start: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '$': + case '@': + { + state = json_location_state::relative_location; + ++p_; + ++column_; + break; + } + default: + { + ec = jsonpath_errc::expected_root_or_current_node; + return path_type{}; + } + } + break; + } + case json_location_state::relative_location: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '[': + separator_kind = selector_separator_kind::bracket; + state = json_location_state::selector; + ++p_; + ++column_; + break; + case '.': + separator_kind = selector_separator_kind::dot; + state = json_location_state::selector; + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_lbracket_or_dot; + return path_type(); + }; + break; + case json_location_state::selector: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '\'': + state = json_location_state::single_quoted_string; + ++p_; + ++column_; + break; + case '\"': + state = json_location_state::double_quoted_string; + ++p_; + ++column_; + break; + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + state = json_location_state::digit; + break; + case '-': + ec = jsonpath_errc::expected_single_quote_or_digit; + return path_type(); + default: + if (separator_kind == selector_separator_kind::dot) + { + state = json_location_state::unquoted_string; + } + else + { + ec = jsonpath_errc::expected_single_quote_or_digit; + return path_type(); + } + break; + } + break; + case json_location_state::single_quoted_string: + switch (*p_) + { + case '\'': + elements.emplace_back(buffer); + buffer.clear(); + if (separator_kind == selector_separator_kind::bracket) + { + state = json_location_state::expect_rbracket; + } + else + { + state = json_location_state::relative_location; + } + ++p_; + ++column_; + break; + case '\\': + state = json_location_state::quoted_string_escape_char; + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case json_location_state::double_quoted_string: + switch (*p_) + { + case '\"': + elements.emplace_back(buffer); + buffer.clear(); + if (separator_kind == selector_separator_kind::bracket) + { + state = json_location_state::expect_rbracket; + } + else + { + state = json_location_state::relative_location; + } + ++p_; + ++column_; + break; + case '\\': + state = json_location_state::quoted_string_escape_char; + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case json_location_state::unquoted_string: + switch (*p_) + { + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'g':case 'h':case 'i':case 'j':case 'k':case 'l':case 'm':case 'n':case 'o':case 'p':case 'q':case 'r':case 's':case 't':case 'u':case 'v':case 'w':case 'x':case 'y':case 'z': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':case 'G':case 'H':case 'I':case 'J':case 'K':case 'L':case 'M':case 'N':case 'O':case 'P':case 'Q':case 'R':case 'S':case 'T':case 'U':case 'V':case 'W':case 'X':case 'Y':case 'Z': + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + case '_': + buffer.push_back(*p_); + ++p_; + ++column_; + break; + case '\\': + state = json_location_state::quoted_string_escape_char; + ++p_; + ++column_; + break; + default: + if (typename std::make_unsigned::type(*p_) > 127) + { + buffer.push_back(*p_); + ++p_; + ++column_; + } + else + { + elements.emplace_back(buffer); + buffer.clear(); + advance_past_space_character(); + state = json_location_state::relative_location; + } + break; + }; + break; + case json_location_state::expect_rbracket: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + state = json_location_state::relative_location; + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_type(alloc_); + } + break; + + case json_location_state::digit: + switch(*p_) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*p_); + ++p_; + ++column_; + break; + default: + std::size_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_type(alloc_); + } + elements.emplace_back(n); + buffer.clear(); + if (separator_kind == selector_separator_kind::bracket) + { + state = json_location_state::expect_rbracket; + } + else + { + state = json_location_state::relative_location; + } + break; + } + break; + case json_location_state::quoted_string_escape_char: + switch (*p_) + { + case '\"': + buffer.push_back('\"'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case '\'': + buffer.push_back('\''); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case '\\': + buffer.push_back('\\'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case '/': + buffer.push_back('/'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case 'b': + buffer.push_back('\b'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case 'f': + buffer.push_back('\f'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case 'n': + buffer.push_back('\n'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case 'r': + buffer.push_back('\r'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case 't': + buffer.push_back('\t'); + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + case 'u': + ++p_; + ++column_; + state = json_location_state::single_quoted_string; + break; + default: + ec = jsonpath_errc::illegal_escaped_character; + return path_type(alloc_); + } + break; + default: + ++p_; + ++column_; + break; + } + } + if (state == json_location_state::unquoted_string) + { + elements.emplace_back(buffer); + } + else if (state == json_location_state::digit) + { + std::size_t n{ 0 }; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_type(alloc_); + } + elements.emplace_back(n); + } + else if (state != json_location_state::relative_location) + { + ec = jsonpath_errc::unexpected_eof; + return path_type(); + } + return path_type(std::move(elements)); + } + + void advance_past_space_character() + { + switch (*p_) + { + case ' ':case '\t': + ++p_; + ++column_; + break; + case '\r': + if (p_+1 < end_input_ && *(p_+1) == '\n') + ++p_; + ++line_; + column_ = 1; + ++p_; + break; + case '\n': + ++line_; + column_ = 1; + ++p_; + break; + default: + break; + } + } + }; + + } // namespace detail + + template > + class basic_json_location + { + public: + using char_type = CharT; + using allocator_type = Allocator; + using string_view_type = jsoncons::basic_string_view>; + using value_type = basic_path_element; + using const_iterator = typename std::vector::const_iterator; + using iterator = const_iterator; + private: + using path_element_allocator_type = typename std::allocator_traits:: template rebind_alloc; + std::vector elements_; + public: + + basic_json_location(const allocator_type& alloc=Allocator()) + : elements_(alloc) + { + } + + explicit basic_json_location(const basic_path_node& path, const allocator_type& alloc=Allocator()) + : elements_(alloc) + { + auto p_node = std::addressof(path); + while (p_node != nullptr) + { + switch (p_node->node_kind()) + { + case path_node_kind::root: + break; + case path_node_kind::name: + elements_.emplace_back(p_node->name().data(), p_node->name().size()); + break; + case path_node_kind::index: + elements_.emplace_back(p_node->index()); + break; + } + p_node = p_node->parent(); + } + std::reverse(elements_.begin(), elements_.end()); + } + + basic_json_location(const basic_json_location&) = default; + + basic_json_location(basic_json_location&&) = default; + + explicit basic_json_location(std::vector&& elements) + : elements_(std::move(elements)) + { + } + + basic_json_location& operator=(const basic_json_location&) = default; + + basic_json_location& operator=(basic_json_location&&) = default; + + // Iterators + + const_iterator begin() const + { + return elements_.begin(); + } + + const_iterator end() const + { + return elements_.end(); + } + + // Accessors + + bool empty() const + { + return elements_.empty(); + } + + std::size_t size() const + { + return elements_.size(); + } + + const value_type& operator[](std::size_t index) const + { + return elements_[index]; + } + + int compare(const basic_json_location& other) const + { + if (this == &other) + { + return 0; + } + + auto it1 = elements_.begin(); + auto it2 = other.elements_.begin(); + while (it1 != elements_.end() && it2 != other.elements_.end()) + { + int diff = it1->compare(*it2); + if (diff != 0) + { + return diff; + } + ++it1; + ++it2; + } + return (elements_.size() < other.elements_.size()) ? -1 : (elements_.size() == other.elements_.size()) ? 0 : 1; + } + + // Modifiers + + void clear() + { + elements_.clear(); + } + + basic_json_location& append(const string_view_type& s) + { + elements_.emplace_back(s.data(), s.size()); + return *this; + } + + template + typename std::enable_if::value, basic_json_location&>::type + append(IntegerType val) + { + elements_.emplace_back(static_cast(val)); + + return *this; + } + + basic_json_location& operator/=(const string_view_type& s) + { + elements_.emplace_back(s.data(), s.size()); + return *this; + } + + template + typename std::enable_if::value, basic_json_location&>::type + operator/=(IntegerType val) + { + elements_.emplace_back(static_cast(val)); + + return *this; + } + + friend bool operator==(const basic_json_location& lhs, const basic_json_location& rhs) + { + return lhs.compare(rhs) == 0; + } + + friend bool operator!=(const basic_json_location& lhs, const basic_json_location& rhs) + { + return !(lhs == rhs); + } + + friend bool operator<(const basic_json_location& lhs, const basic_json_location& rhs) + { + return lhs.compare(rhs) < 0; + } + + static basic_json_location parse(const jsoncons::basic_string_view& normalized_path) + { + jsonpath::detail::json_location_parser> parser; + + std::vector location = parser.parse(normalized_path); + return basic_json_location(std::move(location)); + } + + static basic_json_location parse(const jsoncons::basic_string_view& normalized_path, + std::error_code ec) + { + jsonpath::detail::json_location_parser> parser; + + std::vector location = parser.parse(normalized_path, ec); + if (ec) + { + return basic_json_location(); + } + return basic_json_location(std::move(location)); + } + }; + + template + std::size_t remove(Json& root, const basic_json_location& location) + { + std::size_t count = 0; + + Json* p_current = std::addressof(root); + + std::size_t last = location.size() == 0 ? 0 : location.size() - 1; + for (std::size_t i = 0; i < location.size(); ++i) + { + const auto& element = location[i]; + if (element.has_name()) + { + if (p_current->is_object()) + { + auto it = p_current->find(element.name()); + if (it != p_current->object_range().end()) + { + if (i < last) + { + p_current = std::addressof(it->value()); + } + else + { + p_current->erase(it); + count = 1; + } + } + else + { + break; + } + } + else + { + break; + } + } + else // if (element.has_index()) + { + if (p_current->is_array() && element.index() < p_current->size()) + { + if (i < last) + { + p_current = std::addressof(p_current->at(element.index())); + } + else + { + p_current->erase(p_current->array_range().begin()+element.index()); + count = 1; + } + } + else + { + break; + } + } + } + return count; + } + + template + std::pair get(Json& root, const basic_json_location& location) + { + Json* p_current = std::addressof(root); + bool found = false; + + std::size_t last = location.size() == 0 ? 0 : location.size() - 1; + for (std::size_t i = 0; i < location.size(); ++i) + { + const auto& element = location[i]; + if (element.has_name()) + { + if (p_current->is_object()) + { + auto it = p_current->find(element.name()); + if (it != p_current->object_range().end()) + { + p_current = std::addressof(it->value()); + if (i == last) + { + found = true; + } + } + else + { + break; + } + } + else + { + break; + } + } + else // if (element.has_index()) + { + if (p_current->is_array() && element.index() < p_current->size()) + { + p_current = std::addressof(p_current->at(element.index())); + if (i == last) + { + found = true; + } + } + else + { + break; + } + } + } + return std::make_pair(p_current,found); + } + + template > + std::basic_string, Allocator> to_basic_string(const basic_json_location& location, + const Allocator& alloc = Allocator()) + { + std::basic_string, Allocator> buffer(alloc); + + buffer.push_back('$'); + for (const auto& element : location) + { + if (element.has_name()) + { + buffer.push_back('['); + buffer.push_back('\''); + jsoncons::jsonpath::escape_string(element.name().data(), element.name().size(), buffer); + buffer.push_back('\''); + buffer.push_back(']'); + } + else + { + buffer.push_back('['); + jsoncons::detail::from_integer(element.index(), buffer); + buffer.push_back(']'); + } + } + + return buffer; + } + + template + std::pair replace(Json& root, const basic_json_location& location, const Json& value, + bool create_if_missing=false) + { + Json* p_current = std::addressof(root); + bool found = false; + + std::size_t last = location.size() == 0 ? 0 : location.size() - 1; + for (std::size_t i = 0; i < location.size(); ++i) + { + const auto& element = location[i]; + if (element.has_name() && p_current->is_object()) + { + auto it = p_current->find(element.name()); + if (it != p_current->object_range().end()) + { + p_current = std::addressof(it->value()); + if (i == last) + { + *p_current = std::move(value); + found = true; + } + } + else + { + if (create_if_missing) + { + if (i == last) + { + auto result = p_current->try_emplace(element.name(), value); + p_current = std::addressof(result.first->value()); + found = true; + } + else + { + auto result = p_current->try_emplace(element.name(), Json{}); + p_current = std::addressof(result.first->value()); + } + } + } + } + else if (element.has_index() && p_current->is_array()) + { + if (element.index() < p_current->size()) + { + p_current = std::addressof(p_current->at(element.index())); + if (i == last) + { + *p_current = value; + found = true; + } + } + else + { + break; + } + } + else + { + break; + } + } + if (found) + { + return std::make_pair(p_current,true); + } + else + { + return std::make_pair(p_current, false); + } + } + + using json_location = basic_json_location; + using wjson_location = basic_json_location; + using path_element = basic_path_element>; + using wpath_element = basic_path_element>; + + inline + std::string to_string(const json_location& location) + { + return to_basic_string(location); + } + + inline + std::wstring to_wstring(const wjson_location& location) + { + return to_basic_string(location); + } + +} // namespace jsonpath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/json_location_parser.hpp b/third_party/jsoncons_ext/jsonpath/json_location_parser.hpp new file mode 100644 index 0000000000..1d8d2c095f --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/json_location_parser.hpp @@ -0,0 +1,22 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSON_LOCATION_PARSER_HPP +#define JSONCONS_JSONPATH_JSON_LOCATION_PARSER_HPP + +#include +#include + +namespace jsoncons { +namespace jsonpath { + + + + +} // namespace jsonpath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/json_query.hpp b/third_party/jsoncons_ext/jsonpath/json_query.hpp new file mode 100644 index 0000000000..cb6393024c --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/json_query.hpp @@ -0,0 +1,217 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSON_QUERY_HPP +#define JSONCONS_JSONPATH_JSON_QUERY_HPP + +#include +#include +#include + +namespace jsoncons { +namespace jsonpath { + + template + struct legacy_jsonpath_traits + { + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = typename Json::string_view_type; + using element_type = Json; + using value_type = typename std::remove_cv::type; + using reference = JsonReference; + using const_reference = const value_type&; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + using allocator_type = typename value_type::allocator_type; + using evaluator_type = typename jsoncons::jsonpath::detail::jsonpath_evaluator; + using path_node_type = basic_path_node; + using path_expression_type = jsoncons::jsonpath::detail::path_expression; + using path_pointer = const path_node_type*; + }; + + template + Json json_query(const Json& root, + const typename Json::string_view_type& path, + result_options options = result_options(), + const custom_functions& functions = custom_functions()) + { + auto expr = make_expression(path, functions); + return expr.evaluate(root, options); + } + + template + typename std::enable_if::value,void>::type + json_query(const Json& root, + const typename Json::string_view_type& path, + Callback callback, + result_options options = result_options(), + const custom_functions& functions = custom_functions()) + { + auto expr = make_expression(path, functions); + expr.evaluate(root, callback, options); + } + + template + Json json_query(const allocator_set& alloc_set, + const Json& root, const typename Json::string_view_type& path, + result_options options = result_options(), + const custom_functions& functions = custom_functions()) + { + auto expr = make_expression(alloc_set, path, functions); + return expr.evaluate(root, options); + } + + template + typename std::enable_if::value,void>::type + json_query(const allocator_set& alloc_set, + const Json& root, const typename Json::string_view_type& path, + Callback callback, + result_options options = result_options(), + const custom_functions& functions = custom_functions()) + { + auto expr = make_expression(alloc_set, path, functions); + expr.evaluate(root, callback, options); + } + + template + typename std::enable_if::value,void>::type + json_replace(Json& root, const typename Json::string_view_type& path, T&& new_value, + const custom_functions& funcs = custom_functions()) + { + using jsonpath_traits_type = jsoncons::jsonpath::legacy_jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + using path_node_type = typename jsonpath_traits_type::path_node_type; + + auto static_resources = jsoncons::make_unique>(funcs); + evaluator_type evaluator; + path_expression_type expr = evaluator.compile(*static_resources, path); + + jsoncons::jsonpath::detail::dynamic_resources resources; + auto callback = [&new_value](const path_node_type&, reference v) + { + v = std::forward(new_value); + }; + + result_options options = result_options::nodups | result_options::path | result_options::sort_descending; + expr.evaluate(resources, root, path_node_type{}, root, callback, options); + } + + template + typename std::enable_if::value,void>::type + json_replace(const allocator_set& alloc_set, + Json& root, const typename Json::string_view_type& path, T&& new_value, + const custom_functions& funcs = custom_functions()) + { + using jsonpath_traits_type = jsoncons::jsonpath::legacy_jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + using path_node_type = typename jsonpath_traits_type::path_node_type; + + auto static_resources = jsoncons::make_unique>(funcs, alloc_set.get_allocator()); + evaluator_type evaluator{alloc_set.get_allocator()}; + path_expression_type expr = evaluator.compile(*static_resources, path); + + jsoncons::jsonpath::detail::dynamic_resources resources{alloc_set.get_allocator()}; + auto callback = [&new_value](const path_node_type&, reference v) + { + v = Json(std::forward(new_value), semantic_tag::none); + }; + result_options options = result_options::nodups | result_options::path | result_options::sort_descending; + expr.evaluate(resources, root, path_node_type{}, root, callback, options); + } + + template + typename std::enable_if::value,void>::type + json_replace(Json& root, const typename Json::string_view_type& path , BinaryCallback callback, + const custom_functions& funcs = custom_functions()) + { + using jsonpath_traits_type = jsoncons::jsonpath::legacy_jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + using path_node_type = typename jsonpath_traits_type::path_node_type; + + auto static_resources = jsoncons::make_unique>(funcs); + evaluator_type evaluator; + path_expression_type expr = evaluator.compile(*static_resources, path); + + jsoncons::jsonpath::detail::dynamic_resources resources; + + auto f = [&callback](const path_node_type& path, reference val) + { + callback(to_basic_string(path), val); + }; + result_options options = result_options::nodups | result_options::path | result_options::sort_descending; + expr.evaluate(resources, root, path_node_type{}, root, f, options); + } + + template + typename std::enable_if::value,void>::type + json_replace(const allocator_set& alloc_set, + Json& root, const typename Json::string_view_type& path , BinaryCallback callback, + const custom_functions& funcs = custom_functions()) + { + using jsonpath_traits_type = jsoncons::jsonpath::legacy_jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + using path_node_type = typename jsonpath_traits_type::path_node_type; + + auto static_resources = jsoncons::make_unique>(funcs, alloc_set.get_allocator()); + evaluator_type evaluator{alloc_set.get_allocator()}; + path_expression_type expr = evaluator.compile(*static_resources, path); + + jsoncons::jsonpath::detail::dynamic_resources resources{alloc_set.get_allocator()}; + + auto f = [&callback](const path_node_type& path, reference val) + { + callback(to_basic_string(path), val); + }; + result_options options = result_options::nodups | result_options::path | result_options::sort_descending; + expr.evaluate(resources, root, path_node_type{}, root, f, options); + } + + // Legacy replace function + template + typename std::enable_if::value,void>::type + json_replace(Json& root, const typename Json::string_view_type& path , UnaryCallback callback) + { + using jsonpath_traits_type = jsoncons::jsonpath::legacy_jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + using path_node_type = typename jsonpath_traits_type::path_node_type; + + auto static_resources = jsoncons::make_unique>(); + evaluator_type evaluator; + path_expression_type expr = evaluator.compile(*static_resources, path); + + jsoncons::jsonpath::detail::dynamic_resources resources; + auto f = [callback](const path_node_type&, reference v) + { + v = callback(v); + }; + result_options options = result_options::nodups | result_options::path | result_options::sort_descending; + expr.evaluate(resources, root, path_node_type{}, root, f, options); + } + +} // namespace jsonpath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/jsonpath.hpp b/third_party/jsoncons_ext/jsonpath/jsonpath.hpp new file mode 100644 index 0000000000..3b77ee4f02 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/jsonpath.hpp @@ -0,0 +1,14 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSONPATH_HPP +#define JSONCONS_JSONPATH_JSONPATH_HPP + +#include +#include +#include + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/jsonpath_error.hpp b/third_party/jsoncons_ext/jsonpath/jsonpath_error.hpp new file mode 100644 index 0000000000..6f31f87047 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/jsonpath_error.hpp @@ -0,0 +1,249 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSONPATH_ERROR_HPP +#define JSONCONS_JSONPATH_JSONPATH_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace jsonpath { + + enum class jsonpath_errc + { + success = 0, + expected_root_or_current_node, + expected_lbracket_or_dot, + expected_single_quote_or_digit, + expected_root_or_function, + expected_current_node, + expected_rparen, + expected_rbracket, + expected_separator, + expected_forward_slash, + expected_slice_start, + expected_slice_end, + expected_slice_step, + expected_bracket_specifier_or_union, + unexpected_operator, + invalid_function_name, + invalid_argument, + invalid_arity, + function_name_not_found, + parse_error_in_filter, + argument_parse_error, + unidentified_error, + unexpected_eof, + expected_colon_dot_left_bracket_comma_or_rbracket, + argument_to_unflatten_invalid, + invalid_flattened_key, + step_cannot_be_zero, + invalid_number, + illegal_escaped_character, + invalid_codepoint, + unknown_function, + invalid_type, + unbalanced_parentheses, + syntax_error, + expected_comparator, + expected_or, + expected_and, + expected_comma_or_rparen, + expected_comma_or_rbracket, + expected_relative_path + }; + + class jsonpath_error_category_impl + : public std::error_category + { + public: + const char* name() const noexcept override + { + return "jsoncons/jsonpath"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case jsonpath_errc::expected_root_or_current_node: + return "Expected '$' or '@'"; + case jsonpath_errc::expected_lbracket_or_dot: + return "Expected '[' or '.'"; + case jsonpath_errc::expected_single_quote_or_digit: + return "Expected '\'' or digit"; + case jsonpath_errc::expected_root_or_function: + return "Expected '$' or function expression"; + case jsonpath_errc::expected_current_node: + return "Expected @"; + case jsonpath_errc::expected_rbracket: + return "Expected ]"; + case jsonpath_errc::expected_rparen: + return "Expected )"; + case jsonpath_errc::expected_slice_start: + return "Expected slice start"; + case jsonpath_errc::expected_slice_end: + return "Expected slice end"; + case jsonpath_errc::expected_slice_step: + return "Expected slice step"; + case jsonpath_errc::expected_separator: + return "Expected dot or left bracket separator"; + case jsonpath_errc::expected_forward_slash: + return "Invalid path filter, expected '/'"; + case jsonpath_errc::expected_bracket_specifier_or_union: + return "Expected index, single or double quoted name, expression, filter, absolute ('$') path or relative ('@') path"; + case jsonpath_errc::invalid_function_name: + return "Invalid function name"; + case jsonpath_errc::invalid_argument: + return "Invalid argument type"; + case jsonpath_errc::invalid_arity: + return "Incorrect number of arguments"; + case jsonpath_errc::function_name_not_found: + return "Function name not found"; + case jsonpath_errc::parse_error_in_filter: + return "Could not parse JSON expression in a JSONPath filter"; + case jsonpath_errc::argument_parse_error: + return "Could not parse JSON expression passed to JSONPath function"; + case jsonpath_errc::unidentified_error: + return "Unidentified error"; + case jsonpath_errc::unexpected_eof: + return "Unexpected EOF while parsing jsonpath expression"; + case jsonpath_errc::expected_colon_dot_left_bracket_comma_or_rbracket: + return "Expected ':', '.', '[', ',', or ']'"; + case jsonpath_errc::argument_to_unflatten_invalid: + return "Argument to unflatten must be an object"; + case jsonpath_errc::invalid_flattened_key: + return "Flattened key is invalid"; + case jsonpath_errc::step_cannot_be_zero: + return "Slice step cannot be zero"; + case jsonpath_errc::invalid_number: + return "Invalid number"; + case jsonpath_errc::illegal_escaped_character: + return "Illegal escaped character"; + case jsonpath_errc::invalid_codepoint: + return "Invalid codepoint"; + case jsonpath_errc::unknown_function: + return "Unknown function"; + case jsonpath_errc::invalid_type: + return "Invalid type"; + case jsonpath_errc::unbalanced_parentheses: + return "Unbalanced parentheses"; + case jsonpath_errc::syntax_error: + return "Syntax error"; + case jsonpath_errc::expected_comparator: + return "Expected comparator"; + case jsonpath_errc::expected_or: + return "Expected operator '||'"; + case jsonpath_errc::expected_and: + return "Expected operator '&&'"; + case jsonpath_errc::expected_comma_or_rparen: + return "Expected comma or right parenthesis"; + case jsonpath_errc::expected_comma_or_rbracket: + return "Expected comma or right bracket"; + case jsonpath_errc::expected_relative_path: + return "Expected unquoted string, or single or double quoted string, or index or '*'"; + default: + return "Unknown jsonpath parser error"; + } + } + }; + + inline + const std::error_category& jsonpath_error_category() + { + static jsonpath_error_category_impl instance; + return instance; + } + + inline + std::error_code make_error_code(jsonpath_errc result) + { + return std::error_code(static_cast(result),jsonpath_error_category()); + } + +} // jsonpath +} // jsoncons + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +namespace jsoncons { namespace jsonpath { + + class jsonpath_error : public std::system_error, public virtual json_exception + { + std::size_t line_number_; + std::size_t column_number_; + mutable std::string what_; + public: + jsonpath_error(std::error_code ec) + : std::system_error(ec), line_number_(0), column_number_(0) + { + } + jsonpath_error(std::error_code ec, const std::string& what_arg) + : std::system_error(ec, what_arg), line_number_(0), column_number_(0) + { + } + jsonpath_error(std::error_code ec, std::size_t position) + : std::system_error(ec), line_number_(0), column_number_(position) + { + } + jsonpath_error(std::error_code ec, std::size_t line, std::size_t column) + : std::system_error(ec), line_number_(line), column_number_(column) + { + } + jsonpath_error(const jsonpath_error& other) = default; + + jsonpath_error(jsonpath_error&& other) = default; + + const char* what() const noexcept override + { + if (what_.empty()) + { + JSONCONS_TRY + { + what_.append(std::system_error::what()); + if (line_number_ != 0 && column_number_ != 0) + { + what_.append(" at line "); + what_.append(std::to_string(line_number_)); + what_.append(" and column "); + what_.append(std::to_string(column_number_)); + } + else if (column_number_ != 0) + { + what_.append(" at position "); + what_.append(std::to_string(column_number_)); + } + return what_.c_str(); + } + JSONCONS_CATCH(...) + { + return std::system_error::what(); + } + } + else + { + return what_.c_str(); + } + } + + std::size_t line() const noexcept + { + return line_number_; + } + + std::size_t column() const noexcept + { + return column_number_; + } + }; + +}} + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/jsonpath_expression.hpp b/third_party/jsoncons_ext/jsonpath/jsonpath_expression.hpp new file mode 100644 index 0000000000..7b2910afc1 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/jsonpath_expression.hpp @@ -0,0 +1,267 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSONPATH_EXPR_HPP +#define JSONCONS_JSONPATH_JSONPATH_EXPR_HPP + +#include +#include +#include +#include // std::is_const +#include // std::numeric_limits +#include // std::move +#include +#include // std::reverse +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonpath { + + template + struct jsonpath_traits + { + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = typename Json::string_view_type; + using element_type = Json; + using value_type = typename std::remove_cv::type; + using reference = Json&; + using const_reference = const Json&; + using pointer = typename std::conditional::type>::value,typename Json::const_pointer,typename Json::pointer>::type; + using allocator_type = typename value_type::allocator_type; + using evaluator_type = typename jsoncons::jsonpath::detail::jsonpath_evaluator; + using path_node_type = basic_path_node; + using path_expression_type = jsoncons::jsonpath::detail::path_expression; + using path_pointer = const path_node_type*; + }; + + template > + class jsonpath_expression + { + public: + using jsonpath_traits_type = jsoncons::jsonpath::jsonpath_traits; + + using allocator_type = typename jsonpath_traits_type::allocator_type; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using char_type = typename jsonpath_traits_type::char_type; + using string_type = typename jsonpath_traits_type::string_type; + using string_view_type = typename jsonpath_traits_type::string_view_type; + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using const_reference = typename jsonpath_traits_type::const_reference; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + using path_node_type = typename jsonpath_traits_type::path_node_type; + private: + allocator_type alloc_; + std::unique_ptr> static_resources_; + path_expression_type expr_; + public: + jsonpath_expression(const allocator_set& alloc_set, + std::unique_ptr>&& resources, + path_expression_type&& expr) + : alloc_(alloc_set.get_allocator()), + static_resources_(std::move(resources)), + expr_(std::move(expr)) + { + } + + jsonpath_expression(const jsonpath_expression&) = delete; + jsonpath_expression(jsonpath_expression&&) = default; + + jsonpath_expression& operator=(const jsonpath_expression&) = delete; + jsonpath_expression& operator=(jsonpath_expression&&) = default; + + template + typename std::enable_if::value,void>::type + evaluate(const_reference root, BinaryCallback callback, result_options options = result_options()) const + { + jsoncons::jsonpath::detail::dynamic_resources resources{alloc_}; + auto f = [&callback](const path_node_type& path, reference val) + { + callback(to_basic_string(path), val); + }; + expr_.evaluate(resources, const_cast(root), path_node_type{}, const_cast(root), f, options | result_options::path); + } + + value_type evaluate(const_reference root, result_options options = result_options()) const + { + if ((options & result_options::path) == result_options::path) + { + jsoncons::jsonpath::detail::dynamic_resources resources{ alloc_ }; + + value_type result(json_array_arg, semantic_tag::none, alloc_); + auto callback = [&result](const path_node_type& p, reference) + { + result.emplace_back(to_basic_string(p)); + }; + expr_.evaluate(resources, const_cast(root), + path_node_type{}, const_cast(root), callback, options); + return result; + } + else + { + jsoncons::jsonpath::detail::dynamic_resources resources{ alloc_ }; + return expr_.evaluate(resources, const_cast(root), + path_node_type{}, const_cast(root), options); + } + } + + value_type select(const_reference root, result_options options = result_options()) const + { + if ((options & result_options::path) == result_options::path) + { + jsoncons::jsonpath::detail::dynamic_resources resources{ alloc_ }; + + value_type result(json_array_arg, semantic_tag::none, alloc_); + auto callback = [&result](const path_node_type& p, reference) + { + result.emplace_back(to_basic_string(p)); + }; + expr_.evaluate(resources, const_cast(root), + path_node_type{}, const_cast(root), callback, options); + return result; + } + else + { + jsoncons::jsonpath::detail::dynamic_resources resources{ alloc_ }; + return expr_.evaluate(resources, const_cast(root), + path_node_type{}, const_cast(root), options); + } + } + + template + typename std::enable_if::value,void>::type + select(const_reference root, BinaryCallback callback, result_options options = result_options()) const + { + jsoncons::jsonpath::detail::dynamic_resources resources{alloc_}; + expr_.evaluate(resources, const_cast(root), path_node_type{}, const_cast(root), callback, options | result_options::path); + } + + template + typename std::enable_if::value,void>::type + update(reference root, BinaryCallback callback) const + { + jsoncons::jsonpath::detail::dynamic_resources resources{alloc_}; + + result_options options = result_options::nodups | result_options::path | result_options::sort_descending; + expr_.evaluate(resources, root, path_node_type{}, root, callback, options); + } + + std::vector> select_paths(const_reference root, + result_options options = result_options::nodups | result_options::sort) const + { + std::vector> result; + + options = options | result_options::path; + + auto callback = [&result](const path_node_type& path, const_reference) + { + result.emplace_back(path); + }; + + jsoncons::jsonpath::detail::dynamic_resources resources{alloc_}; + expr_.evaluate(resources, const_cast(root), path_node_type{}, const_cast(root), callback, options); + + return result; + } + }; + + template + jsonpath_expression make_expression(const typename Json::string_view_type& path, + const jsoncons::jsonpath::custom_functions::value_type>& funcs = jsoncons::jsonpath::custom_functions::value_type>()) + { + using jsonpath_traits_type = jsoncons::jsonpath::jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + + auto static_resources = jsoncons::make_unique>(funcs); + evaluator_type evaluator; + auto expr = evaluator.compile(*static_resources, path); + + return jsonpath_expression(jsoncons::combine_allocators(), std::move(static_resources), std::move(expr)); + } + + template + jsonpath_expression make_expression(const typename Json::string_view_type& expr, std::error_code& ec) + { + return make_expression(jsoncons::combine_allocators(), expr, custom_functions(), ec); + } + + template + jsonpath_expression make_expression(const allocator_set& alloc_set, + const typename Json::string_view_type& expr, std::error_code& ec) + { + return make_expression(alloc_set, expr, custom_functions(), ec); + } + + template + jsonpath_expression make_expression(const allocator_set& alloc_set, + const typename Json::string_view_type& path, + const custom_functions& functions = custom_functions()) + { + using jsonpath_traits_type = jsoncons::jsonpath::jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + + auto resources = jsoncons::make_unique>(functions, + alloc_set.get_allocator()); + + evaluator_type evaluator{alloc_set.get_allocator()}; + path_expression_type expr = evaluator.compile(*resources, path); + return jsonpath_expression(alloc_set, std::move(resources), std::move(expr)); + + } + + template + jsonpath_expression make_expression(const allocator_set& alloc_set, + const typename Json::string_view_type& path, + const jsoncons::jsonpath::custom_functions::value_type>& funcs, std::error_code& ec) + { + using jsonpath_traits_type = jsoncons::jsonpath::jsonpath_traits; + + using value_type = typename jsonpath_traits_type::value_type; + using reference = typename jsonpath_traits_type::reference; + using evaluator_type = typename jsonpath_traits_type::evaluator_type; + using path_expression_type = typename jsonpath_traits_type::path_expression_type; + + auto resources = jsoncons::make_unique>(funcs, + alloc_set.get_allocator()); + evaluator_type evaluator{alloc_set.get_allocator()}; + path_expression_type expr = evaluator.compile(*resources, path, ec); + + return jsonpath_expression(alloc_set, std::move(resources), std::move(expr)); + } + + template + std::size_t remove(Json& root, const jsoncons::basic_string_view& path_string) + { + std::size_t count = 0; + + auto expr = jsonpath::make_expression(path_string); + std::vector locations = expr.select_paths(root, + jsonpath::result_options::nodups | jsonpath::result_options::sort_descending); + + for (const auto& location : locations) + { + std::size_t n = jsonpath::remove(root, location); + count += n; + } + return count; + } + +} // namespace jsonpath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/jsonpath_parser.hpp b/third_party/jsoncons_ext/jsonpath/jsonpath_parser.hpp new file mode 100644 index 0000000000..e79ca8b099 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/jsonpath_parser.hpp @@ -0,0 +1,2488 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSONPATH_EXPRESSION_HPP +#define JSONCONS_JSONPATH_JSONPATH_EXPRESSION_HPP + +#include +#include +#include +#include // std::is_const +#include // std::numeric_limits +#include // std::move +#include +#include // std::reverse +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonpath { +namespace detail { + + enum class path_state + { + start, + root_or_current_node, + expect_function_expr, + relative_path, + relative_location, + parent_operator, + ancestor_depth, + filter_expression, + expression_rhs, + recursive_descent_or_expression_lhs, + path_or_literal_or_function, + json_text_or_function, + json_text_or_function_name, + json_text_string, + json_value, + json_text, + identifier_or_function_expr, + name_or_lbracket, + unquoted_string, + anything, + number, + function_expression, + argument, + zero_or_one_arguments, + one_or_more_arguments, + identifier, + single_quoted_string, + double_quoted_string, + bracketed_unquoted_name_or_union, + union_expression, + identifier_or_union, + bracket_specifier_or_union, + bracketed_wildcard, + index_or_slice, + wildcard_or_union, + union_element, + index_or_slice_or_union, + integer, + digit, + slice_expression_stop, + slice_expression_step, + comma_or_rbracket, + expect_rparen, + expect_rbracket, + quoted_string_escape_char, + escape_u1, + escape_u2, + escape_u3, + escape_u4, + escape_expect_surrogate_pair1, + escape_expect_surrogate_pair2, + escape_u5, + escape_u6, + escape_u7, + escape_u8, + expression, + comparator_expression, + eq_or_regex, + expect_regex, + regex, + regex_options, + regex_pattern, + cmp_lt_or_lte, + cmp_gt_or_gte, + cmp_ne, + expect_or, + expect_and + }; + + template + class jsonpath_evaluator : public ser_context + { + public: + using allocator_type = typename Json::allocator_type; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = typename Json::string_view_type; + using path_value_pair_type = path_value_pair; + using value_type = Json; + using reference = JsonReference; + using pointer = typename path_value_pair_type::value_pointer; + using token_type = token; + using path_expression_type = path_expression; + using expression_type = expression; + using path_node_type = basic_path_node; + using selector_type = jsonpath_selector; + + private: + + allocator_type alloc_; + std::size_t line_; + std::size_t column_; + const char_type* begin_input_; + const char_type* end_input_; + const char_type* p_; + + using argument_type = std::vector; + std::vector function_stack_; + std::vector state_stack_; + std::vector output_stack_; + std::vector operator_stack_; + + public: + jsonpath_evaluator(const allocator_type& alloc = allocator_type()) + : alloc_(alloc), line_(1), column_(1), + begin_input_(nullptr), end_input_(nullptr), + p_(nullptr) + { + } + + jsonpath_evaluator(std::size_t line, std::size_t column, + const allocator_type& alloc = allocator_type()) + : alloc_(alloc), line_(line), column_(column), + begin_input_(nullptr), end_input_(nullptr), + p_(nullptr) + { + } + + std::size_t line() const + { + return line_; + } + + std::size_t column() const + { + return column_; + } + + path_expression_type compile(static_resources& resources, const string_view_type& path) + { + std::error_code ec; + auto result = compile(resources, path, ec); + if (ec) + { + JSONCONS_THROW(jsonpath_error(ec, line_, column_)); + } + return result; + } + + path_expression_type compile(static_resources& resources, + const string_view_type& path, + std::error_code& ec) + { + std::size_t selector_id = 0; + + string_type buffer(alloc_); + string_type buffer2(alloc_); + uint32_t cp = 0; + uint32_t cp2 = 0; + + begin_input_ = path.data(); + end_input_ = path.data() + path.length(); + p_ = begin_input_; + + slice slic; + bool paths_required = false; + int ancestor_depth = 0; + + state_stack_.emplace_back(path_state::start); + while (p_ < end_input_ && !state_stack_.empty()) + { + switch (state_stack_.back()) + { + case path_state::start: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '$': + case '@': + { + push_token(resources, token_type(resources.new_selector(current_node_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.emplace_back(path_state::relative_location); + ++p_; + ++column_; + break; + } + default: + { + state_stack_.emplace_back(path_state::relative_location); + state_stack_.emplace_back(path_state::expect_function_expr); + state_stack_.emplace_back(path_state::unquoted_string); + break; + } + } + break; + } + case path_state::root_or_current_node: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '$': + push_token(resources, token_type(root_node_arg), ec); + push_token(resources, token_type(resources.new_selector(root_selector(selector_id++))), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case '@': + push_token(resources, token_type(current_node_arg), ec); // ISSUE + push_token(resources, token_type(resources.new_selector(current_node_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::syntax_error; + return path_expression_type(alloc_); + } + break; + case path_state::recursive_descent_or_expression_lhs: + switch (*p_) + { + case '.': + push_token(resources, token_type(resources.new_selector(recursive_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + ++p_; + ++column_; + state_stack_.back() = path_state::name_or_lbracket; + break; + default: + state_stack_.back() = path_state::relative_path; + break; + } + break; + case path_state::name_or_lbracket: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '[': // [ can follow .. + state_stack_.back() = path_state::bracket_specifier_or_union; + ++p_; + ++column_; + break; + default: + buffer.clear(); + state_stack_.back() = path_state::relative_path; + break; + } + break; + case path_state::json_text: + { + //std::cout << "literal: " << buffer << "\n"; + push_token(resources, token_type(literal_arg, Json(buffer,semantic_tag::none,alloc_)), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); // json_value + break; + } + case path_state::path_or_literal_or_function: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '$': + case '@': + state_stack_.back() = path_state::relative_location; + state_stack_.push_back(path_state::root_or_current_node); + break; + case '(': + { + ++p_; + ++column_; + push_token(resources, lparen_arg, ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::expect_rparen; + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + break; + } + case '\'': + state_stack_.back() = path_state::json_text; + state_stack_.emplace_back(path_state::single_quoted_string); + ++p_; + ++column_; + break; + case '\"': + state_stack_.back() = path_state::json_text; + state_stack_.emplace_back(path_state::double_quoted_string); + ++p_; + ++column_; + break; + case '!': + { + ++p_; + ++column_; + push_token(resources, token_type(resources.get_unary_not()), ec); + if (ec) {return path_expression_type(alloc_);} + break; + } + case '-': + { + ++p_; + ++column_; + push_token(resources, token_type(resources.get_unary_minus()), ec); + if (ec) {return path_expression_type(alloc_);} + break; + } + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + { + state_stack_.back() = path_state::json_value; + state_stack_.emplace_back(path_state::number); + break; + } + default: + { + state_stack_.back() = path_state::json_text_or_function_name; + break; + } + } + break; + } + case path_state::json_text_or_function: + { + switch(*p_) + { + case '(': + { + auto f = resources.get_function(buffer, ec); + if (ec) + { + return path_expression_type(alloc_); + } + buffer.clear(); + push_token(resources, current_node_arg, ec); + if (ec) {return path_expression_type(alloc_);} + push_token(resources, token_type(f), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::function_expression; + state_stack_.emplace_back(path_state::zero_or_one_arguments); + ++p_; + ++column_; + break; + } + default: + { + json_decoder decoder(alloc_); + basic_json_parser parser; + parser.update(buffer.data(),buffer.size()); + parser.parse_some(decoder, ec); + if (ec) + { + return path_expression_type(alloc_); + } + parser.finish_parse(decoder, ec); + if (ec) + { + return path_expression_type(alloc_); + } + push_token(resources, token_type(literal_arg, decoder.get_result()), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + break; + } + } + break; + } + case path_state::json_value: + { + json_decoder decoder(alloc_); + basic_json_parser parser; + parser.update(buffer.data(),buffer.size()); + parser.parse_some(decoder, ec); + if (ec) + { + return path_expression_type(alloc_); + } + parser.finish_parse(decoder, ec); + if (ec) + { + return path_expression_type(alloc_); + } + push_token(resources, token_type(literal_arg, decoder.get_result()), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + break; + } + case path_state::json_text_or_function_name: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '{': + case '[': + { + json_decoder decoder(alloc_); + basic_json_parser parser; + parser.update(p_,end_input_ - p_); + parser.parse_some(decoder, ec); + if (ec) + { + return path_expression_type(alloc_); + } + parser.finish_parse(decoder, ec); + if (ec) + { + return path_expression_type(alloc_); + } + push_token(resources, token_type(literal_arg, decoder.get_result()), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + p_ = parser.current(); + column_ = column_ + parser.column() - 1; + break; + } + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + state_stack_.back() = path_state::json_text_or_function; + state_stack_.emplace_back(path_state::number); + buffer.push_back(*p_); + ++p_; + ++column_; + break; + case '\"': + state_stack_.back() = path_state::json_text_or_function; + state_stack_.emplace_back(path_state::json_text_string); + buffer.push_back(*p_); + ++p_; + ++column_; + break; + default: + state_stack_.back() = path_state::json_text_or_function; + state_stack_.emplace_back(path_state::unquoted_string); + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case path_state::number: + switch (*p_) + { + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + case 'e':case 'E':case '.': + buffer.push_back(*p_); + ++p_; + ++column_; + break; + default: + state_stack_.pop_back(); // number + break; + }; + break; + case path_state::json_text_string: + switch (*p_) + { + case '\\': + buffer.push_back(*p_); + ++p_; + ++column_; + if (p_ == end_input_) + { + ec = jsonpath_errc::unexpected_eof; + return path_expression_type(alloc_); + } + buffer.push_back(*p_); + ++p_; + ++column_; + break; + case '\"': + buffer.push_back(*p_); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case path_state::relative_path: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '*': + push_token(resources, token_type(resources.new_selector(wildcard_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case '\'': + state_stack_.back() = path_state::identifier; + state_stack_.emplace_back(path_state::single_quoted_string); + ++p_; + ++column_; + break; + case '\"': + state_stack_.back() = path_state::identifier; + state_stack_.emplace_back(path_state::double_quoted_string); + ++p_; + ++column_; + break; + case '[': + case '.': + ec = jsonpath_errc::expected_relative_path; + return path_expression_type(alloc_); + default: + buffer.clear(); + state_stack_.back() = path_state::identifier_or_function_expr; + state_stack_.emplace_back(path_state::unquoted_string); + break; + } + break; + case path_state::identifier_or_function_expr: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '(': + { + auto f = resources.get_function(buffer, ec); + if (ec) + { + return path_expression_type(alloc_); + } + buffer.clear(); + push_token(resources, current_node_arg, ec); + push_token(resources, token_type(f), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::function_expression; + state_stack_.emplace_back(path_state::zero_or_one_arguments); + ++p_; + ++column_; + break; + } + default: + { + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + break; + } + } + break; + } + case path_state::expect_function_expr: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '(': + { + auto f = resources.get_function(buffer, ec); + if (ec) + { + return path_expression_type(alloc_); + } + buffer.clear(); + push_token(resources, current_node_arg, ec); + push_token(resources, token_type(f), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::function_expression; + state_stack_.emplace_back(path_state::zero_or_one_arguments); + ++p_; + ++column_; + break; + } + default: + { + ec = jsonpath_errc::expected_root_or_function; + return path_expression_type(alloc_); + } + } + break; + } + case path_state::function_expression: + { + + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + push_token(resources, token_type(current_node_arg), ec); + if (ec) {return path_expression_type(alloc_);} + push_token(resources, token_type(begin_expression_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.emplace_back(path_state::argument); + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + ++p_; + ++column_; + break; + case ')': + { + push_token(resources, token_type(end_function_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + } + default: + ec = jsonpath_errc::syntax_error; + return path_expression_type(alloc_); + } + break; + } + case path_state::zero_or_one_arguments: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ')': + state_stack_.pop_back(); + break; + default: + push_token(resources, token_type(begin_expression_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::one_or_more_arguments; + state_stack_.emplace_back(path_state::argument); + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + break; + } + break; + } + case path_state::one_or_more_arguments: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ')': + state_stack_.pop_back(); + break; + case ',': + push_token(resources, token_type(begin_expression_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.emplace_back(path_state::argument); + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + ++p_; + ++column_; + break; + } + break; + } + case path_state::argument: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + case ')': + { + push_token(resources, token_type(end_argument_expression_arg), ec); + push_token(resources, argument_arg, ec); + //push_token(resources, argument_arg, ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + break; + } + default: + ec = jsonpath_errc::expected_comma_or_rparen; + return path_expression_type(alloc_); + } + break; + } + case path_state::unquoted_string: + switch (*p_) + { + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f':case 'g':case 'h':case 'i':case 'j':case 'k':case 'l':case 'm':case 'n':case 'o':case 'p':case 'q':case 'r':case 's':case 't':case 'u':case 'v':case 'w':case 'x':case 'y':case 'z': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F':case 'G':case 'H':case 'I':case 'J':case 'K':case 'L':case 'M':case 'N':case 'O':case 'P':case 'Q':case 'R':case 'S':case 'T':case 'U':case 'V':case 'W':case 'X':case 'Y':case 'Z': + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + case '_': + buffer.push_back(*p_); + ++p_; + ++column_; + break; + default: + if (typename std::make_unsigned::type(*p_) > 127) + { + buffer.push_back(*p_); + ++p_; + ++column_; + } + else + { + state_stack_.pop_back(); // unquoted_string + } + break; + }; + break; + case path_state::relative_location: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '.': + state_stack_.emplace_back(path_state::recursive_descent_or_expression_lhs); + ++p_; + ++column_; + break; + case '[': + state_stack_.emplace_back(path_state::bracket_specifier_or_union); + ++p_; + ++column_; + break; + case '^': + ancestor_depth = 0; + state_stack_.emplace_back(path_state::parent_operator); + state_stack_.emplace_back(path_state::ancestor_depth); + break; + default: + state_stack_.pop_back(); + break; + }; + break; + case path_state::parent_operator: + { + push_token(resources, token_type(resources.new_selector(parent_node_selector(ancestor_depth))), ec); + paths_required = true; + ancestor_depth = 0; + ++p_; + ++column_; + state_stack_.pop_back(); + break; + } + case path_state::ancestor_depth: + { + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '^': + { + ++ancestor_depth; + ++p_; + ++column_; + break; + } + default: + { + state_stack_.pop_back(); + break; + } + } + break; + } + case path_state::expression_rhs: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '.': + state_stack_.emplace_back(path_state::recursive_descent_or_expression_lhs); + ++p_; + ++column_; + break; + case '[': + state_stack_.emplace_back(path_state::bracket_specifier_or_union); + ++p_; + ++column_; + break; + case ')': + { + state_stack_.pop_back(); + break; + } + case '|': + ++p_; + ++column_; + state_stack_.emplace_back(path_state::path_or_literal_or_function); + state_stack_.emplace_back(path_state::expect_or); + break; + case '&': + ++p_; + ++column_; + state_stack_.emplace_back(path_state::path_or_literal_or_function); + state_stack_.emplace_back(path_state::expect_and); + break; + case '<': + case '>': + { + state_stack_.emplace_back(path_state::comparator_expression); + break; + } + case '=': + { + state_stack_.emplace_back(path_state::eq_or_regex); + ++p_; + ++column_; + break; + } + case '!': + { + ++p_; + ++column_; + state_stack_.emplace_back(path_state::path_or_literal_or_function); + state_stack_.emplace_back(path_state::cmp_ne); + break; + } + case '+': + state_stack_.emplace_back(path_state::path_or_literal_or_function); + push_token(resources, token_type(resources.get_plus_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + ++p_; + ++column_; + break; + case '-': + state_stack_.emplace_back(path_state::path_or_literal_or_function); + push_token(resources, token_type(resources.get_minus_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + ++p_; + ++column_; + break; + case '*': + state_stack_.emplace_back(path_state::path_or_literal_or_function); + push_token(resources, token_type(resources.get_mult_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + ++p_; + ++column_; + break; + case '/': + state_stack_.emplace_back(path_state::path_or_literal_or_function); + push_token(resources, token_type(resources.get_div_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + ++p_; + ++column_; + break; + case '%': + state_stack_.emplace_back(path_state::path_or_literal_or_function); + push_token(resources, token_type(resources.get_modulus_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + ++p_; + ++column_; + break; + case ']': + case ',': + state_stack_.pop_back(); + break; + default: + ec = jsonpath_errc::expected_separator; + return path_expression_type(alloc_); + }; + break; + case path_state::expect_or: + { + switch (*p_) + { + case '|': + push_token(resources, token_type(resources.get_or_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_or; + return path_expression_type(alloc_); + } + break; + } + case path_state::expect_and: + { + switch(*p_) + { + case '&': + push_token(resources, token_type(resources.get_and_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); // expect_and + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_and; + return path_expression_type(alloc_); + } + break; + } + case path_state::comparator_expression: + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '<': + ++p_; + ++column_; + state_stack_.back() = path_state::path_or_literal_or_function; + state_stack_.emplace_back(path_state::cmp_lt_or_lte); + break; + case '>': + ++p_; + ++column_; + state_stack_.back() = path_state::path_or_literal_or_function; + state_stack_.emplace_back(path_state::cmp_gt_or_gte); + break; + default: + if (state_stack_.size() > 1) + { + state_stack_.pop_back(); + } + else + { + ec = jsonpath_errc::syntax_error; + return path_expression_type(alloc_); + } + break; + } + break; + case path_state::eq_or_regex: + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '=': + { + push_token(resources, token_type(resources.get_eq_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::path_or_literal_or_function; + ++p_; + ++column_; + break; + } + case '~': + { + ++p_; + ++column_; + state_stack_.emplace_back(path_state::expect_regex); + break; + } + default: + if (state_stack_.size() > 1) + { + state_stack_.pop_back(); + } + else + { + ec = jsonpath_errc::syntax_error; + return path_expression_type(alloc_); + } + break; + } + break; + case path_state::expect_regex: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '/': + state_stack_.back() = path_state::regex; + state_stack_.emplace_back(path_state::regex_options); + state_stack_.emplace_back(path_state::regex_pattern); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_forward_slash; + return path_expression_type(alloc_); + }; + break; + case path_state::regex: + { + std::regex::flag_type options = std::regex_constants::ECMAScript; + if (buffer2.find('i') != string_type::npos) + { + options |= std::regex_constants::icase; + } + std::basic_regex pattern(buffer, options); + push_token(resources, resources.get_regex_operator(std::move(pattern)), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + buffer2.clear(); + state_stack_.pop_back(); + break; + } + case path_state::regex_pattern: + { + switch (*p_) + { + case '/': + { + state_stack_.pop_back(); + ++p_; + ++column_; + } + break; + + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + } + break; + } + case path_state::regex_options: + { + if (*p_ == 'i') + { + buffer2.push_back(*p_); + ++p_; + ++column_; + } + else + { + state_stack_.pop_back(); + } + break; + } + case path_state::cmp_lt_or_lte: + { + switch(*p_) + { + case '=': + push_token(resources, token_type(resources.get_lte_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + push_token(resources, token_type(resources.get_lt_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + break; + } + break; + } + case path_state::cmp_gt_or_gte: + { + switch(*p_) + { + case '=': + push_token(resources, token_type(resources.get_gte_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + //std::cout << "Parse: gt_operator\n"; + push_token(resources, token_type(resources.get_gt_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + break; + } + break; + } + case path_state::cmp_ne: + { + switch(*p_) + { + case '=': + push_token(resources, token_type(resources.get_ne_operator()), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_comparator; + return path_expression_type(alloc_); + } + break; + } + case path_state::identifier: + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + break; + case path_state::single_quoted_string: + switch (*p_) + { + case '\'': + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case '\\': + state_stack_.emplace_back(path_state::quoted_string_escape_char); + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case path_state::double_quoted_string: + switch (*p_) + { + case '\"': + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case '\\': + state_stack_.emplace_back(path_state::quoted_string_escape_char); + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + }; + break; + case path_state::comma_or_rbracket: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + state_stack_.back() = path_state::bracket_specifier_or_union; + ++p_; + ++column_; + break; + case ']': + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_comma_or_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::expect_rbracket: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::expect_rparen: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ')': + ++p_; + ++column_; + push_token(resources, rparen_arg, ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::expression_rhs; + break; + default: + ec = jsonpath_errc::expected_rparen; + return path_expression_type(alloc_); + } + break; + case path_state::bracket_specifier_or_union: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '(': + { + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(begin_expression_arg), ec); + push_token(resources, lparen_arg, ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::expression); + state_stack_.emplace_back(path_state::expect_rparen); + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + ++p_; + ++column_; + break; + } + case '?': + { + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(begin_filter_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::filter_expression); + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + ++p_; + ++column_; + break; + } + case '*': + state_stack_.back() = path_state::wildcard_or_union; + ++p_; + ++column_; + break; + case '\'': + state_stack_.back() = path_state::identifier_or_union; + state_stack_.push_back(path_state::single_quoted_string); + ++p_; + ++column_; + break; + case '\"': + state_stack_.back() = path_state::identifier_or_union; + state_stack_.push_back(path_state::double_quoted_string); + ++p_; + ++column_; + break; + case ':': // slice_expression + state_stack_.back() = path_state::index_or_slice_or_union; + break; + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + state_stack_.back() = path_state::index_or_slice_or_union; + state_stack_.emplace_back(path_state::integer); + break; + case '$': + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, root_node_arg, ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::relative_location); + ++p_; + ++column_; + break; + case '@': + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(current_node_arg), ec); // ISSUE + push_token(resources, token_type(resources.new_selector(current_node_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::relative_location); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_bracket_specifier_or_union; + return path_expression_type(alloc_); + } + break; + case path_state::union_element: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ':': // slice_expression + state_stack_.back() = path_state::index_or_slice; + break; + case '-':case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + state_stack_.back() = path_state::index_or_slice; + state_stack_.emplace_back(path_state::integer); + break; + case '(': + { + push_token(resources, token_type(begin_expression_arg), ec); + push_token(resources, lparen_arg, ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::expression; + state_stack_.emplace_back(path_state::expect_rparen); + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + ++p_; + ++column_; + break; + } + case '?': + { + push_token(resources, token_type(begin_filter_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::filter_expression; + state_stack_.emplace_back(path_state::expression_rhs); + state_stack_.emplace_back(path_state::path_or_literal_or_function); + ++p_; + ++column_; + break; + } + case '*': + push_token(resources, token_type(resources.new_selector(wildcard_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::relative_location; + ++p_; + ++column_; + break; + case '$': + push_token(resources, token_type(root_node_arg), ec); + push_token(resources, token_type(resources.new_selector(root_selector(selector_id++))), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::relative_location; + ++p_; + ++column_; + break; + case '@': + push_token(resources, token_type(current_node_arg), ec); // ISSUE + push_token(resources, token_type(resources.new_selector(current_node_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::relative_location; + ++p_; + ++column_; + break; + case '\'': + state_stack_.back() = path_state::identifier; + state_stack_.push_back(path_state::single_quoted_string); + ++p_; + ++column_; + break; + case '\"': + state_stack_.back() = path_state::identifier; + state_stack_.push_back(path_state::double_quoted_string); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_bracket_specifier_or_union; + return path_expression_type(alloc_); + } + break; + + case path_state::integer: + switch(*p_) + { + case '-': + buffer.push_back(*p_); + state_stack_.back() = path_state::digit; + ++p_; + ++column_; + break; + default: + state_stack_.back() = path_state::digit; + break; + } + break; + case path_state::digit: + switch(*p_) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8':case '9': + buffer.push_back(*p_); + ++p_; + ++column_; + break; + default: + state_stack_.pop_back(); // digit + break; + } + break; + case path_state::index_or_slice_or_union: + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + { + if (buffer.empty()) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + int64_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + push_token(resources, token_type(resources.new_selector(index_selector(n))), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); // index_or_slice_or_union + ++p_; + ++column_; + break; + } + case ',': + { + push_token(resources, token_type(begin_union_arg), ec); + if (ec) {return path_expression_type(alloc_);} + if (buffer.empty()) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + else + { + int64_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + push_token(resources, token_type(resources.new_selector(index_selector(n))), ec); + if (ec) {return path_expression_type(alloc_);} + + buffer.clear(); + } + push_token(resources, token_type(separator_arg), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::union_element); + ++p_; + ++column_; + break; + } + case ':': + { + if (!buffer.empty()) + { + int64_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + slic.start_ = n; + buffer.clear(); + } + push_token(resources, token_type(begin_union_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::slice_expression_stop); + state_stack_.emplace_back(path_state::integer); + ++p_; + ++column_; + break; + } + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::slice_expression_stop: + { + if (!buffer.empty()) + { + int64_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + slic.stop_ = jsoncons::optional(n); + buffer.clear(); + } + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + case ',': + push_token(resources, token_type(resources.new_selector(slice_selector(slic))), ec); + if (ec) {return path_expression_type(alloc_);} + slic = slice{}; + state_stack_.pop_back(); // bracket_specifier2 + break; + case ':': + state_stack_.back() = path_state::slice_expression_step; + state_stack_.emplace_back(path_state::integer); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + } + case path_state::slice_expression_step: + { + if (!buffer.empty()) + { + int64_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + if (n == 0) + { + ec = jsonpath_errc::step_cannot_be_zero; + return path_expression_type(alloc_); + } + slic.step_ = n; + buffer.clear(); + } + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + case ',': + push_token(resources, token_type(resources.new_selector(slice_selector(slic))), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + slic = slice{}; + state_stack_.pop_back(); // slice_expression_step + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + } + + case path_state::bracketed_unquoted_name_or_union: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case '.': + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::relative_path); + ++p_; + ++column_; + break; + case '[': + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::relative_path); + ++p_; + ++column_; + break; + case ',': + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + push_token(resources, token_type(separator_arg), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::relative_path); + ++p_; + ++column_; + break; + default: + buffer.push_back(*p_); + ++p_; + ++column_; + break; + } + break; + case path_state::union_expression: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '.': + state_stack_.emplace_back(path_state::relative_path); + ++p_; + ++column_; + break; + case '[': + state_stack_.emplace_back(path_state::bracket_specifier_or_union); + ++p_; + ++column_; + break; + case ',': + push_token(resources, token_type(separator_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.emplace_back(path_state::union_element); + ++p_; + ++column_; + break; + case ']': + push_token(resources, token_type(end_union_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::identifier_or_union: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case ',': + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + push_token(resources, token_type(separator_arg), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::union_element); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::bracketed_wildcard: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case '[': + case ']': + case ',': + case '.': + push_token(resources, token_type(resources.new_selector(wildcard_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::index_or_slice: + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + case ']': + { + if (buffer.empty()) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + else + { + int64_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + push_token(resources, token_type(resources.new_selector(index_selector(n))), ec); + if (ec) {return path_expression_type(alloc_);} + + buffer.clear(); + } + state_stack_.pop_back(); // bracket_specifier + break; + } + case ':': + { + if (!buffer.empty()) + { + int64_t n{0}; + auto r = jsoncons::detail::to_integer(buffer.data(), buffer.size(), n); + if (!r) + { + ec = jsonpath_errc::invalid_number; + return path_expression_type(alloc_); + } + slic.start_ = n; + buffer.clear(); + } + state_stack_.back() = path_state::slice_expression_stop; + state_stack_.emplace_back(path_state::integer); + ++p_; + ++column_; + break; + } + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::wildcard_or_union: + switch (*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ']': + push_token(resources, token_type(resources.new_selector(wildcard_selector())), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + case ',': + push_token(resources, token_type(begin_union_arg), ec); + push_token(resources, token_type(resources.new_selector(wildcard_selector())), ec); + push_token(resources, token_type(separator_arg), ec); + if (ec) {return path_expression_type(alloc_);} + buffer.clear(); + state_stack_.back() = path_state::union_expression; // union + state_stack_.emplace_back(path_state::union_element); + ++p_; + ++column_; + break; + default: + ec = jsonpath_errc::expected_rbracket; + return path_expression_type(alloc_); + } + break; + case path_state::quoted_string_escape_char: + switch (*p_) + { + case '\"': + buffer.push_back('\"'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case '\'': + buffer.push_back('\''); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case '\\': + buffer.push_back('\\'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case '/': + buffer.push_back('/'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'b': + buffer.push_back('\b'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'f': + buffer.push_back('\f'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'n': + buffer.push_back('\n'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'r': + buffer.push_back('\r'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 't': + buffer.push_back('\t'); + ++p_; + ++column_; + state_stack_.pop_back(); + break; + case 'u': + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u1; + break; + default: + ec = jsonpath_errc::illegal_escaped_character; + return path_expression_type(alloc_); + } + break; + case path_state::escape_u1: + cp = append_to_codepoint(0, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u2; + break; + case path_state::escape_u2: + cp = append_to_codepoint(cp, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u3; + break; + case path_state::escape_u3: + cp = append_to_codepoint(cp, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u4; + break; + case path_state::escape_u4: + cp = append_to_codepoint(cp, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + if (unicode_traits::is_high_surrogate(cp)) + { + ++p_; + ++column_; + state_stack_.back() = path_state::escape_expect_surrogate_pair1; + } + else + { + unicode_traits::convert(&cp, 1, buffer); + ++p_; + ++column_; + state_stack_.pop_back(); + } + break; + case path_state::escape_expect_surrogate_pair1: + switch (*p_) + { + case '\\': + ++p_; + ++column_; + state_stack_.back() = path_state::escape_expect_surrogate_pair2; + break; + default: + ec = jsonpath_errc::invalid_codepoint; + return path_expression_type(alloc_); + } + break; + case path_state::escape_expect_surrogate_pair2: + switch (*p_) + { + case 'u': + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u5; + break; + default: + ec = jsonpath_errc::invalid_codepoint; + return path_expression_type(alloc_); + } + break; + case path_state::escape_u5: + cp2 = append_to_codepoint(0, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u6; + break; + case path_state::escape_u6: + cp2 = append_to_codepoint(cp2, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u7; + break; + case path_state::escape_u7: + cp2 = append_to_codepoint(cp2, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + ++p_; + ++column_; + state_stack_.back() = path_state::escape_u8; + break; + case path_state::escape_u8: + { + cp2 = append_to_codepoint(cp2, *p_, ec); + if (ec) + { + return path_expression_type(alloc_); + } + uint32_t codepoint = 0x10000 + ((cp & 0x3FF) << 10) + (cp2 & 0x3FF); + unicode_traits::convert(&codepoint, 1, buffer); + state_stack_.pop_back(); + ++p_; + ++column_; + break; + } + case path_state::filter_expression: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + case ']': + { + push_token(resources, token_type(end_filter_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + break; + } + default: + ec = jsonpath_errc::expected_comma_or_rbracket; + return path_expression_type(alloc_); + } + break; + } + case path_state::expression: + { + switch(*p_) + { + case ' ':case '\t':case '\r':case '\n': + advance_past_space_character(); + break; + case ',': + case ']': + { + push_token(resources, token_type(end_index_expression_arg), ec); + if (ec) {return path_expression_type(alloc_);} + state_stack_.pop_back(); + break; + } + default: + ec = jsonpath_errc::expected_comma_or_rbracket; + return path_expression_type(alloc_); + } + break; + } + default: + ++p_; + ++column_; + break; + } + } + + if (state_stack_.empty()) + { + ec = jsonpath_errc::syntax_error; + return path_expression_type(alloc_); + } + + while (state_stack_.size() > 1) + { + switch (state_stack_.back()) + { + case path_state::name_or_lbracket: + state_stack_.back() = path_state::relative_path; + break; + case path_state::relative_path: + state_stack_.back() = path_state::identifier_or_function_expr; + state_stack_.emplace_back(path_state::unquoted_string); + break; + case path_state::identifier_or_function_expr: + if (!buffer.empty()) // Can't be quoted string + { + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + } + state_stack_.pop_back(); + break; + case path_state::unquoted_string: + state_stack_.pop_back(); // unquoted_string + break; + case path_state::relative_location: + state_stack_.pop_back(); + break; + case path_state::identifier: + if (!buffer.empty()) // Can't be quoted string + { + push_token(resources, token_type(resources.new_selector(identifier_selector(buffer))), ec); + if (ec) {return path_expression_type(alloc_);} + } + state_stack_.pop_back(); + break; + case path_state::parent_operator: + { + push_token(resources, token_type(resources.new_selector(parent_node_selector(ancestor_depth))), ec); + if (ec) { return path_expression_type(alloc_); } + paths_required = true; + state_stack_.pop_back(); + break; + } + case path_state::ancestor_depth: + state_stack_.pop_back(); + break; + default: + ec = jsonpath_errc::syntax_error; + return path_expression_type(alloc_); + } + } + + if (state_stack_.size() > 2) + { + ec = jsonpath_errc::unexpected_eof; + return path_expression_type(alloc_); + } + + //std::cout << "\nTokens\n\n"; + //for (const auto& tok : output_stack_) + //{ + // std::cout << tok.to_string() << "\n"; + //} + //std::cout << "\n"; + + if (output_stack_.empty() || !operator_stack_.empty()) + { + ec = jsonpath_errc::unexpected_eof; + return path_expression_type(alloc_); + } + + return path_expression_type(output_stack_.back().selector_, paths_required, alloc_); + } + + void advance_past_space_character() + { + switch (*p_) + { + case ' ':case '\t': + ++p_; + ++column_; + break; + case '\r': + if (p_+1 < end_input_ && *(p_+1) == '\n') + ++p_; + ++line_; + column_ = 1; + ++p_; + break; + case '\n': + ++line_; + column_ = 1; + ++p_; + break; + default: + break; + } + } + + void unwind_rparen(std::error_code& ec) + { + auto it = operator_stack_.rbegin(); + while (it != operator_stack_.rend() && !it->is_lparen()) + { + output_stack_.emplace_back(std::move(*it)); + ++it; + } + if (it == operator_stack_.rend()) + { + ec = jsonpath_errc::unbalanced_parentheses; + return; + } + ++it; + operator_stack_.erase(it.base(),operator_stack_.end()); + } + + void push_token(jsoncons::jsonpath::detail::static_resources& resources, token_type&& tok, std::error_code& ec) + { + //std::cout << tok.to_string() << "\n"; + switch (tok.token_kind()) + { + case jsonpath_token_kind::begin_filter: + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token_type(lparen_arg)); + break; + case jsonpath_token_kind::end_filter: + { + //std::cout << "push_token end_filter 1\n"; + //for (const auto& tok2 : output_stack_) + //{ + // std::cout << tok2.to_string() << "\n"; + //} + //std::cout << "\n\n"; + unwind_rparen(ec); + if (ec) + { + return; + } + std::vector toks; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->token_kind() != jsonpath_token_kind::begin_filter) + { + toks.emplace_back(std::move(*it)); + ++it; + } + if (it == output_stack_.rend()) + { + ec = jsonpath_errc::unbalanced_parentheses; + return; + } + std::reverse(toks.begin(), toks.end()); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + + if (!output_stack_.empty() && output_stack_.back().is_path()) + { + output_stack_.back().selector_->append_selector(resources.new_selector(filter_selector(expression_type(std::move(toks))))); + } + else + { + output_stack_.emplace_back(token_type(resources.new_selector(filter_selector(expression_type(std::move(toks)))))); + } + //std::cout << "push_token end_filter 2\n"; + //for (const auto& tok2 : output_stack_) + //{ + // std::cout << tok2.to_string() << "\n"; + //} + //std::cout << "\n\n"; + break; + } + case jsonpath_token_kind::begin_expression: + //std::cout << "begin_expression\n"; + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token_type(lparen_arg)); + break; + case jsonpath_token_kind::end_index_expression: + { + //std::cout << "jsonpath_token_kind::end_index_expression\n"; + //for (const auto& t : output_stack_) + //{ + // std::cout << t.to_string() << "\n"; + //} + //std::cout << "/jsonpath_token_kind::end_index_expression\n"; + unwind_rparen(ec); + if (ec) + { + return; + } + std::vector toks; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->token_kind() != jsonpath_token_kind::begin_expression) + { + toks.emplace_back(std::move(*it)); + ++it; + } + if (it == output_stack_.rend()) + { + ec = jsonpath_errc::unbalanced_parentheses; + return; + } + std::reverse(toks.begin(), toks.end()); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + + if (!output_stack_.empty() && output_stack_.back().is_path()) + { + output_stack_.back().selector_->append_selector(resources.new_selector(index_expression_selector(expression_type(std::move(toks))))); + } + else + { + output_stack_.emplace_back(token_type(resources.new_selector(index_expression_selector(expression_type(std::move(toks)))))); + } + break; + } + case jsonpath_token_kind::end_argument_expression: + { + //std::cout << "jsonpath_token_kind::end_index_expression\n"; + //for (const auto& t : output_stack_) + //{ + // std::cout << t.to_string() << "\n"; + //} + //std::cout << "/jsonpath_token_kind::end_index_expression\n"; + unwind_rparen(ec); + if (ec) + { + return; + } + std::vector toks; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->token_kind() != jsonpath_token_kind::begin_expression) + { + toks.emplace_back(std::move(*it)); + ++it; + } + if (it == output_stack_.rend()) + { + ec = jsonpath_errc::unbalanced_parentheses; + return; + } + std::reverse(toks.begin(), toks.end()); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + output_stack_.emplace_back(token_type(jsoncons::make_unique(std::move(toks)))); + break; + } + case jsonpath_token_kind::selector: + { + if (!output_stack_.empty() && output_stack_.back().is_path()) + { + output_stack_.back().selector_->append_selector(std::move(tok.selector_)); + } + else + { + output_stack_.emplace_back(std::move(tok)); + } + break; + } + case jsonpath_token_kind::separator: + output_stack_.emplace_back(std::move(tok)); + break; + case jsonpath_token_kind::begin_union: + output_stack_.emplace_back(std::move(tok)); + break; + case jsonpath_token_kind::end_union: + { + std::vector expressions; + auto it = output_stack_.rbegin(); + while (it != output_stack_.rend() && it->token_kind() != jsonpath_token_kind::begin_union) + { + if (it->token_kind() == jsonpath_token_kind::selector) + { + expressions.emplace_back(std::move(it->selector_)); + } + do + { + ++it; + } + while (it != output_stack_.rend() && it->token_kind() != jsonpath_token_kind::begin_union && it->token_kind() != jsonpath_token_kind::separator); + if (it->token_kind() == jsonpath_token_kind::separator) + { + ++it; + } + } + if (it == output_stack_.rend()) + { + ec = jsonpath_errc::unbalanced_parentheses; + return; + } + std::reverse(expressions.begin(), expressions.end()); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + + if (!output_stack_.empty() && output_stack_.back().is_path()) + { + output_stack_.back().selector_->append_selector(resources.new_selector(union_selector(std::move(expressions)))); + } + else + { + output_stack_.emplace_back(token_type(resources.new_selector(union_selector(std::move(expressions))))); + } + break; + } + case jsonpath_token_kind::lparen: + operator_stack_.emplace_back(std::move(tok)); + break; + case jsonpath_token_kind::rparen: + { + unwind_rparen(ec); + break; + } + case jsonpath_token_kind::end_function: + { + //std::cout << "jsonpath_token_kind::end_function\n"; + unwind_rparen(ec); + if (ec) + { + return; + } + std::vector toks; + auto it = output_stack_.rbegin(); + std::size_t arg_count = 0; + while (it != output_stack_.rend() && it->token_kind() != jsonpath_token_kind::function) + { + if (it->token_kind() == jsonpath_token_kind::argument) + { + ++arg_count; + } + toks.emplace_back(std::move(*it)); + ++it; + } + if (it == output_stack_.rend()) + { + ec = jsonpath_errc::unbalanced_parentheses; + return; + } + std::reverse(toks.begin(), toks.end()); + if (it->arity() && arg_count != *(it->arity())) + { + ec = jsonpath_errc::invalid_arity; + return; + } + toks.push_back(std::move(*it)); + ++it; + output_stack_.erase(it.base(),output_stack_.end()); + + if (!output_stack_.empty() && output_stack_.back().is_path()) + { + output_stack_.back().selector_->append_selector(resources.new_selector(function_selector(expression_type(std::move(toks))))); + } + else + { + output_stack_.emplace_back(token_type(resources.new_selector(function_selector(std::move(toks))))); + } + break; + } + case jsonpath_token_kind::literal: + if (!output_stack_.empty() && (output_stack_.back().token_kind() == jsonpath_token_kind::current_node || output_stack_.back().token_kind() == jsonpath_token_kind::root_node)) + { + output_stack_.back() = std::move(tok); + } + else + { + output_stack_.emplace_back(std::move(tok)); + } + break; + case jsonpath_token_kind::function: + output_stack_.emplace_back(std::move(tok)); + operator_stack_.emplace_back(token_type(lparen_arg)); + break; + case jsonpath_token_kind::argument: + output_stack_.emplace_back(std::move(tok)); + break; + case jsonpath_token_kind::root_node: + case jsonpath_token_kind::current_node: + output_stack_.emplace_back(std::move(tok)); + break; + case jsonpath_token_kind::unary_operator: + case jsonpath_token_kind::binary_operator: + { + if (operator_stack_.empty() || operator_stack_.back().is_lparen()) + { + operator_stack_.emplace_back(std::move(tok)); + } + else if (tok.precedence_level() < operator_stack_.back().precedence_level() + || (tok.precedence_level() == operator_stack_.back().precedence_level() && tok.is_right_associative())) + { + operator_stack_.emplace_back(std::move(tok)); + } + else + { + auto it = operator_stack_.rbegin(); + while (it != operator_stack_.rend() && it->is_operator() + && (tok.precedence_level() > it->precedence_level() + || (tok.precedence_level() == it->precedence_level() && tok.is_right_associative()))) + { + output_stack_.emplace_back(std::move(*it)); + ++it; + } + + operator_stack_.erase(it.base(),operator_stack_.end()); + operator_stack_.emplace_back(std::move(tok)); + } + break; + } + default: + break; + } + //std::cout << " " << "Output Stack\n"; + //for (auto&& t : output_stack_) + //{ + // std::cout << t.to_string(2) << "\n"; + //} + //if (!operator_stack_.empty()) + //{ + // std::cout << " " << "Operator Stack\n"; + // for (auto&& t : operator_stack_) + // { + // std::cout << t.to_string(2) << "\n"; + // } + //} + } + + uint32_t append_to_codepoint(uint32_t cp, int c, std::error_code& ec) + { + cp *= 16; + if (c >= '0' && c <= '9') + { + cp += c - '0'; + } + else if (c >= 'a' && c <= 'f') + { + cp += c - 'a' + 10; + } + else if (c >= 'A' && c <= 'F') + { + cp += c - 'A' + 10; + } + else + { + ec = jsonpath_errc::invalid_codepoint; + } + return cp; + } + }; + + } // namespace detail + +} // namespace jsonpath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/jsonpath_selector.hpp b/third_party/jsoncons_ext/jsonpath/jsonpath_selector.hpp new file mode 100644 index 0000000000..8aa4079158 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/jsonpath_selector.hpp @@ -0,0 +1,1319 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_JSONPATH_SELECTOR_HPP +#define JSONCONS_JSONPATH_JSONPATH_SELECTOR_HPP + +#include +#include +#include +#include // std::is_const +#include // std::numeric_limits +#include // std::move +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonpath { +namespace detail { + + struct slice + { + jsoncons::optional start_; + jsoncons::optional stop_; + int64_t step_; + + slice() + : start_(), stop_(), step_(1) + { + } + + slice(const jsoncons::optional& start, const jsoncons::optional& end, int64_t step) + : start_(start), stop_(end), step_(step) + { + } + + slice(const slice& other) + : start_(other.start_), stop_(other.stop_), step_(other.step_) + { + } + + slice& operator=(const slice& rhs) + { + if (this != &rhs) + { + if (rhs.start_) + { + start_ = rhs.start_; + } + else + { + start_.reset(); + } + if (rhs.stop_) + { + stop_ = rhs.stop_; + } + else + { + stop_.reset(); + } + step_ = rhs.step_; + } + return *this; + } + + int64_t get_start(std::size_t size) const + { + if (start_) + { + auto len = *start_ >= 0 ? *start_ : (static_cast(size) + *start_); + return len <= static_cast(size) ? len : static_cast(size); + } + else + { + if (step_ >= 0) + { + return 0; + } + else + { + return static_cast(size); + } + } + } + + int64_t get_stop(std::size_t size) const + { + if (stop_) + { + auto len = *stop_ >= 0 ? *stop_ : (static_cast(size) + *stop_); + return len <= static_cast(size) ? len : static_cast(size); + } + else + { + return step_ >= 0 ? static_cast(size) : -1; + } + } + + int64_t step() const + { + return step_; // Allow negative + } + }; + + template + class json_array_receiver : public node_receiver + { + public: + using reference = JsonReference; + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using path_node_type = basic_path_node; + + Json* val; + + json_array_receiver(Json* ptr) + : val(ptr) + { + } + + void add(const path_node_type&, reference value) override + { + val->emplace_back(value); + } + }; + + template + struct path_generator + { + using char_type = typename Json::char_type; + using string_view_type = typename Json::string_view_type; + using string_type = typename Json::string_type; + using path_node_type = basic_path_node; + + static const path_node_type& generate(dynamic_resources& resources, + const path_node_type& last, + std::size_t index, + result_options options) + { + const result_options require_path = result_options::path | result_options::nodups | result_options::sort; + if ((options & require_path) != result_options()) + { + return *resources.create_path_node(&last, index); + } + else + { + return last; + } + } + + static const path_node_type& generate(dynamic_resources& resources, + const path_node_type& last, + const string_view_type& identifier, + result_options options) + { + const result_options require_path = result_options::path | result_options::nodups | result_options::sort; + if ((options & require_path) != result_options()) + { + return *resources.create_path_node(&last, identifier); + } + else + { + return last; + } + } + }; + + template + class base_selector : public jsonpath_selector + { + using supertype = jsonpath_selector; + + supertype* tail_; + public: + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using node_receiver_type = typename supertype::node_receiver_type; + using selector_type = typename supertype::selector_type; + + base_selector() + : supertype(true, 11), tail_(nullptr) + { + } + + base_selector(bool is_path, std::size_t precedence_level) + : supertype(is_path, precedence_level), tail_(nullptr) + { + } + + void append_selector(selector_type* expr) override + { + if (!tail_) + { + tail_ = expr; + } + else + { + tail_->append_selector(expr); + } + } + + void tail_select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const + { + if (!tail_) + { + receiver.add(last, current); + } + else + { + tail_->select(resources, root, last, current, receiver, options); + } + } + + reference evaluate_tail(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code& ec) const + { + if (!tail_) + { + return current; + } + else + { + return tail_->evaluate(resources, root, last, current, options, ec); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + if (tail_) + { + s.append(tail_->to_string(level)); + } + return s; + } + }; + + template + class identifier_selector final : public base_selector + { + using supertype = base_selector; + using path_generator_type = path_generator; + public: + using char_type = typename Json::char_type; + using string_type = typename Json::string_type; + using string_view_type = typename Json::string_view_type; + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using node_receiver_type = typename supertype::node_receiver_type; + private: + string_type identifier_; + public: + + identifier_selector(const string_type& identifier) + : base_selector(), identifier_(identifier) + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + if (current.is_object()) + { + auto it = current.find(identifier_); + if (it != current.object_range().end()) + { + this->tail_select(resources, root, + path_generator_type::generate(resources, last, identifier_, options), + it->value(), receiver, options); + } + } + else if (current.is_array()) + { + int64_t n{0}; + auto r = jsoncons::detail::decimal_to_integer(identifier_.data(), identifier_.size(), n); + if (r) + { + std::size_t index = (n >= 0) ? static_cast(n) : static_cast(static_cast(current.size()) + n); + if (index < current.size()) + { + this->tail_select(resources, root, + path_generator_type::generate(resources, last, index, options), + current[index], receiver, options); + } + } + else if (identifier_ == resources.length_label() && current.size() >= 0) + { + pointer ptr = resources.create_json(current.size(), semantic_tag::none, resources.get_allocator()); + this->tail_select(resources, root, + path_generator_type::generate(resources, last, identifier_, options), + *ptr, + receiver, options); + } + } + else if (current.is_string() && identifier_ == resources.length_label()) + { + string_view_type sv = current.as_string_view(); + std::size_t count = unicode_traits::count_codepoints(sv.data(), sv.size()); + pointer ptr = resources.create_json(count, semantic_tag::none, resources.get_allocator()); + this->tail_select(resources, root, + path_generator_type::generate(resources, last, identifier_, options), + *ptr, receiver, options); + } + //std::cout << "end identifier_selector\n"; + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code& ec) const override + { + if (current.is_object()) + { + auto it = current.find(identifier_); + if (it != current.object_range().end()) + { + return this->evaluate_tail(resources, root, + path_generator_type::generate(resources, last, identifier_, options), + it->value(), options, ec); + } + else + { + return resources.null_value(); + } + } + else if (current.is_array()) + { + int64_t n{0}; + auto r = jsoncons::detail::decimal_to_integer(identifier_.data(), identifier_.size(), n); + if (r) + { + std::size_t index = (n >= 0) ? static_cast(n) : static_cast(static_cast(current.size()) + n); + if (index < current.size()) + { + return this->evaluate_tail(resources, root, + path_generator_type::generate(resources, last, index, options), + current[index], options, ec); + } + else + { + return resources.null_value(); + } + } + else if (identifier_ == resources.length_label() && current.size() > 0) + { + pointer ptr = resources.create_json(current.size(), semantic_tag::none, resources.get_allocator()); + return this->evaluate_tail(resources, root, + path_generator_type::generate(resources, last, identifier_, options), + *ptr, + options, ec); + } + else + { + return resources.null_value(); + } + } + else if (current.is_string() && identifier_ == resources.length_label()) + { + string_view_type sv = current.as_string_view(); + std::size_t count = unicode_traits::count_codepoints(sv.data(), sv.size()); + pointer ptr = resources.create_json(count, semantic_tag::none, resources.get_allocator()); + return this->evaluate_tail(resources, root, + path_generator_type::generate(resources, last, identifier_, options), + *ptr, options, ec); + } + else + { + return resources.null_value(); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("identifier selector "); + unicode_traits::convert(identifier_.data(),identifier_.size(),s); + s.append(base_selector::to_string(level+1)); + //s.append("\n"); + + return s; + } + }; + + template + class root_selector final : public base_selector + { + using supertype = base_selector; + using path_generator_type = path_generator; + + std::size_t id_; + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using node_receiver_type = typename supertype::node_receiver_type; + + root_selector(std::size_t id) + : base_selector(), id_(id) + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference, + node_receiver_type& receiver, + result_options options) const override + { + this->tail_select(resources, root, last, root, receiver, options); + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference, + result_options options, + std::error_code& ec) const override + { + if (resources.is_cached(id_)) + { + return resources.retrieve_from_cache(id_); + } + else + { + auto& ref = this->evaluate_tail(resources, root, last, root, options, ec); + if (!ec) + { + resources.add_to_cache(id_, ref); + } + + return ref; + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("root_selector "); + s.append(base_selector::to_string(level+1)); + + return s; + } + }; + + template + class current_node_selector final : public base_selector + { + using supertype = base_selector; + + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + current_node_selector() + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + this->tail_select(resources, + root, last, current, receiver, options); + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code& ec) const override + { + //std::cout << "current_node_selector: " << current << "\n"; + return this->evaluate_tail(resources, + root, last, current, options, ec); + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("current_node_selector"); + s.append(base_selector::to_string(level+1)); + + return s; + } + }; + + template + class parent_node_selector final : public base_selector + { + using supertype = base_selector; + using allocator_type = typename Json::allocator_type; + + int ancestor_depth_; + + public: + using char_type = typename Json::char_type; + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + parent_node_selector(int ancestor_depth) + { + ancestor_depth_ = ancestor_depth; + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference, + node_receiver_type& receiver, + result_options options) const override + { + const path_node_type* ancestor = std::addressof(last); + int index = 0; + while (ancestor != nullptr && index < ancestor_depth_) + { + ancestor = ancestor->parent(); + ++index; + } + + if (ancestor != nullptr) + { + pointer ptr = jsoncons::jsonpath::select(root,*ancestor); + if (ptr != nullptr) + { + this->tail_select(resources, root, *ancestor, *ptr, receiver, options); + } + } + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference, + result_options options, + std::error_code& ec) const override + { + const path_node_type* ancestor = std::addressof(last); + int index = 0; + while (ancestor != nullptr && index < ancestor_depth_) + { + ancestor = ancestor->parent(); + ++index; + } + + if (ancestor != nullptr) + { + pointer ptr = jsoncons::jsonpath::select(root, *ancestor); + if (ptr != nullptr) + { + return this->evaluate_tail(resources, root, *ancestor, *ptr, options, ec); + } + else + { + return resources.null_value(); + } + } + else + { + return resources.null_value(); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("parent_node_selector"); + s.append(base_selector::to_string(level+1)); + + return s; + } + }; + + template + class index_selector final : public base_selector + { + using supertype = base_selector; + + int64_t index_; + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + index_selector(int64_t index) + : base_selector(), index_(index) + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + if (current.is_array()) + { + int64_t slen = static_cast(current.size()); + if (index_ >= 0 && index_ < slen) + { + std::size_t i = static_cast(index_); + this->tail_select(resources, root, + path_generator_type::generate(resources, last, i, options), + current.at(i), receiver, options); + } + else + { + int64_t index = slen + index_; + if (index >= 0 && index < slen) + { + std::size_t i = static_cast(index); + this->tail_select(resources, root, + path_generator_type::generate(resources, last, i, options), + current.at(i), receiver, options); + } + } + } + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code& ec) const override + { + if (current.is_array()) + { + int64_t slen = static_cast(current.size()); + if (index_ >= 0 && index_ < slen) + { + std::size_t i = static_cast(index_); + return this->evaluate_tail(resources, root, + path_generator_type::generate(resources, last, i, options), + current.at(i), options, ec); + } + else + { + int64_t index = slen + index_; + if (index >= 0 && index < slen) + { + std::size_t i = static_cast(index); + return this->evaluate_tail(resources, root, + path_generator_type::generate(resources, last, i, options), + current.at(i), options, ec); + } + else + { + return resources.null_value(); + } + } + } + else + { + return resources.null_value(); + } + } + }; + + template + class wildcard_selector final : public base_selector + { + using supertype = base_selector; + + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + wildcard_selector() + : base_selector() + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + if (current.is_array()) + { + for (std::size_t i = 0; i < current.size(); ++i) + { + this->tail_select(resources, root, + path_generator_type::generate(resources, last, i, options), current[i], + receiver, options); + } + } + else if (current.is_object()) + { + for (auto& member : current.object_range()) + { + this->tail_select(resources, root, + path_generator_type::generate(resources, last, member.key(), options), + member.value(), receiver, options); + } + } + //std::cout << "end wildcard_selector\n"; + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code&) const override + { + auto jptr = resources.create_json(json_array_arg, semantic_tag::none, resources.get_allocator()); + json_array_receiver receiver(jptr); + select(resources, root, last, current, receiver, options); + return *jptr; + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("wildcard selector"); + s.append(base_selector::to_string(level)); + + return s; + } + }; + + template + class recursive_selector final : public base_selector + { + using supertype = base_selector; + + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + recursive_selector() + : base_selector() + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + if (current.is_array()) + { + this->tail_select(resources, root, last, current, receiver, options); + for (std::size_t i = 0; i < current.size(); ++i) + { + select(resources, root, + path_generator_type::generate(resources, last, i, options), current[i], receiver, options); + } + } + else if (current.is_object()) + { + this->tail_select(resources, root, last, current, receiver, options); + for (auto& item : current.object_range()) + { + select(resources, root, + path_generator_type::generate(resources, last, item.key(), options), item.value(), receiver, options); + } + } + //std::cout << "end wildcard_selector\n"; + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code&) const override + { + auto jptr = resources.create_json(json_array_arg, semantic_tag::none, resources.get_allocator()); + json_array_receiver receiver(jptr); + select(resources, root, last, current, receiver, options); + return *jptr; + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("wildcard selector"); + s.append(base_selector::to_string(level)); + + return s; + } + }; + + template + class union_selector final : public jsonpath_selector + { + using supertype = jsonpath_selector; + public: + using char_type = typename Json::char_type; + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_expression_type = path_expression; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + using selector_type = typename supertype::selector_type; + private: + std::vector selectors_; + selector_type* tail_; + public: + union_selector(std::vector&& selectors) + : supertype(true, 11), selectors_(std::move(selectors)), tail_(nullptr) + { + } + + void append_selector(selector_type* tail) override + { + if (tail_ == nullptr) + { + tail_ = tail; + for (auto& selector : selectors_) + { + selector->append_selector(tail); + } + } + else + { + tail_->append_selector(tail); + } + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + for (auto& selector : selectors_) + { + selector->select(resources, root, last, current, receiver, options); + } + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code&) const override + { + auto jptr = resources.create_json(json_array_arg, semantic_tag::none, resources.get_allocator()); + json_array_receiver receiver(jptr); + select(resources,root,last,current,receiver,options); + return *jptr; + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("union selector "); + for (auto& selector : selectors_) + { + s.append(selector->to_string(level+1)); + //s.push_back('\n'); + } + + return s; + } + }; + + template + class filter_selector final : public base_selector + { + using supertype = base_selector; + + expression expr_; + + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + filter_selector(expression&& expr) + : base_selector(), expr_(std::move(expr)) + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + if (current.is_array()) + { + for (std::size_t i = 0; i < current.size(); ++i) + { + std::error_code ec; + value_type r = expr_.evaluate(resources, root, current[i], options, ec); + bool t = ec ? false : detail::is_true(r); + if (t) + { + this->tail_select(resources, root, + path_generator_type::generate(resources, last, i, options), + current[i], receiver, options); + } + } + } + else if (current.is_object()) + { + for (auto& member : current.object_range()) + { + std::error_code ec; + value_type r = expr_.evaluate(resources, root, member.value(), options, ec); + bool t = ec ? false : detail::is_true(r); + if (t) + { + this->tail_select(resources, root, + path_generator_type::generate(resources, last, member.key(), options), + member.value(), receiver, options); + } + } + } + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code&) const override + { + auto jptr = resources.create_json(json_array_arg, semantic_tag::none, resources.get_allocator()); + json_array_receiver receiver(jptr); + select(resources, root, last, current, receiver, options); + return *jptr; + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("filter selector "); + s.append(expr_.to_string(level+1)); + + return s; + } + }; + + template + class index_expression_selector final : public base_selector + { + using supertype = base_selector; + using allocator_type = typename Json::allocator_type; + using string_type = typename Json::string_type; + + expression expr_; + + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + index_expression_selector(expression&& expr) + : base_selector(), expr_(std::move(expr)) + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + std::error_code ec; + value_type j = expr_.evaluate(resources, root, current, options, ec); + + if (!ec) + { + if (j.template is() && current.is_array()) + { + std::size_t start = j.template as(); + this->tail_select(resources, root, + path_generator_type::generate(resources, last, start, options), + current.at(start), receiver, options); + } + else if (j.is_string() && current.is_object()) + { + auto sv = j.as_string_view(); + this->tail_select(resources, root, + path_generator_type::generate(resources, last, sv, options), + current.at(j.as_string_view()), receiver, options); + } + } + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code& ec) const override + { + //std::cout << "index_expression_selector current: " << current << "\n"; + + value_type j = expr_.evaluate(resources, root, current, options, ec); + + if (!ec) + { + if (j.template is() && current.is_array()) + { + std::size_t start = j.template as(); + return this->evaluate_tail(resources, root, last, current.at(start), options, ec); + } + else if (j.is_string() && current.is_object()) + { + return this->evaluate_tail(resources, root, last, current.at(j.as_string_view()), options, ec); + } + else + { + return resources.null_value(); + } + } + else + { + return resources.null_value(); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("bracket expression selector "); + s.append(expr_.to_string(level+1)); + s.append(base_selector::to_string(level+1)); + + return s; + } + }; + + template + class slice_selector final : public base_selector + { + using supertype = base_selector; + using path_generator_type = path_generator; + + slice slice_; + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using node_receiver_type = typename supertype::node_receiver_type; + + slice_selector(const slice& slic) + : base_selector(), slice_(slic) + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + if (current.is_array()) + { + auto start = slice_.get_start(current.size()); + auto end = slice_.get_stop(current.size()); + auto step = slice_.step(); + + if (step > 0) + { + if (start < 0) + { + start = 0; + } + if (end > static_cast(current.size())) + { + end = current.size(); + } + for (int64_t i = start; i < end; i += step) + { + std::size_t j = static_cast(i); + this->tail_select(resources, root, + path_generator_type::generate(resources, last, j, options), + current[j], receiver, options); + } + } + else if (step < 0) + { + if (start >= static_cast(current.size())) + { + start = static_cast(current.size()) - 1; + } + if (end < -1) + { + end = -1; + } + for (int64_t i = start; i > end; i += step) + { + std::size_t j = static_cast(i); + if (j < current.size()) + { + this->tail_select(resources, root, + path_generator_type::generate(resources, last,j,options), current[j], receiver, options); + } + } + } + } + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code&) const override + { + auto jptr = resources.create_json(json_array_arg, semantic_tag::none, resources.get_allocator()); + json_array_receiver accum(jptr); + select(resources, root, last, current, accum, options); + return *jptr; + } + }; + + template + class function_selector final : public base_selector + { + using supertype = base_selector; + + expression expr_; + + public: + using value_type = typename supertype::value_type; + using reference = typename supertype::reference; + using pointer = typename supertype::pointer; + using path_value_pair_type = typename supertype::path_value_pair_type; + using path_node_type = typename supertype::path_node_type; + using path_generator_type = path_generator; + using node_receiver_type = typename supertype::node_receiver_type; + + function_selector(expression&& expr) + : base_selector(), expr_(std::move(expr)) + { + } + + void select(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + node_receiver_type& receiver, + result_options options) const override + { + std::error_code ec; + value_type ref = expr_.evaluate(resources, root, current, options, ec); + if (!ec) + { + this->tail_select(resources, root, last, *resources.create_json(std::move(ref)), receiver, options); + } + } + + reference evaluate(dynamic_resources& resources, + reference root, + const path_node_type& last, + reference current, + result_options options, + std::error_code& ec) const override + { + value_type ref = expr_.evaluate(resources, root, current, options, ec); + if (!ec) + { + return this->evaluate_tail(resources, root, last, *resources.create_json(std::move(ref)), + options, ec); + } + else + { + return resources.null_value(); + } + } + + std::string to_string(int level = 0) const override + { + std::string s; + if (level > 0) + { + s.append("\n"); + s.append(level*2, ' '); + } + s.append("function_selector "); + s.append(expr_.to_string(level+1)); + + return s; + } + }; + +} // namespace detail +} // namespace jsonpath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/jsonpath_utilities.hpp b/third_party/jsoncons_ext/jsonpath/jsonpath_utilities.hpp new file mode 100644 index 0000000000..9e7cd1822e --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/jsonpath_utilities.hpp @@ -0,0 +1,77 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_UTILITIES_HPP +#define JSONCONS_JSONPATH_UTILITIES_HPP + +#include +#include +#include +#include // std::is_const +#include // std::numeric_limits +#include // std::move +#include // std::copy +#include // std::back_inserter + +namespace jsoncons { namespace jsonpath { + + template + std::size_t escape_string(const CharT* s, std::size_t length, Sink& sink) + { + std::size_t count = 0; + const CharT* begin = s; + const CharT* end = s + length; + for (const CharT* it = begin; it != end; ++it) + { + CharT c = *it; + switch (c) + { + case '\\': + sink.push_back('\\'); + sink.push_back('\\'); + count += 2; + break; + case '\'': + sink.push_back('\\'); + sink.push_back('\''); + count += 2; + break; + case '\b': + sink.push_back('\\'); + sink.push_back('b'); + count += 2; + break; + case '\f': + sink.push_back('\\'); + sink.push_back('f'); + count += 2; + break; + case '\n': + sink.push_back('\\'); + sink.push_back('n'); + count += 2; + break; + case '\r': + sink.push_back('\\'); + sink.push_back('r'); + count += 2; + break; + case '\t': + sink.push_back('\\'); + sink.push_back('t'); + count += 2; + break; + default: + sink.push_back(c); + ++count; + break; + } + } + return count; + } +}} + +#endif diff --git a/third_party/jsoncons_ext/jsonpath/path_node.hpp b/third_party/jsoncons_ext/jsonpath/path_node.hpp new file mode 100644 index 0000000000..241e1811f7 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpath/path_node.hpp @@ -0,0 +1,339 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPATH_PATH_NODE_HPP +#define JSONCONS_JSONPATH_PATH_NODE_HPP + +#include +#include +#include +#include // std::reverse +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonpath { + + enum class path_node_kind { root, name, index }; + + template + class basic_path_node + { + public: + using string_view_type = jsoncons::basic_string_view; + using char_type = CharT; + private: + + const basic_path_node* parent_; + std::size_t size_; + path_node_kind node_kind_; + string_view_type name_; + std::size_t index_; + + public: + basic_path_node() + : parent_(nullptr), size_(1), + node_kind_(path_node_kind::root), + index_(0) + { + } + + basic_path_node(const basic_path_node* parent, string_view_type name) + : parent_(parent), size_(parent == nullptr ? 1 : parent->size()+1), + node_kind_(path_node_kind::name), name_(name), index_(0) + { + } + + basic_path_node(const basic_path_node* parent, std::size_t index) + : parent_(parent), size_(parent == nullptr ? 1 : parent->size()+1), + node_kind_(path_node_kind::index), index_(index) + { + } + + basic_path_node(const basic_path_node& other) + : parent_(other.parent_), size_(other.size()), + node_kind_(other.node_kind_), + name_(other.name_), + index_(other.index_) + { + } + + basic_path_node& operator=(const basic_path_node& other) + { + parent_ = other.parent_; + size_ = other.size(); + node_kind_ = other.node_kind_; + index_ = other.index_; + name_ = other.name_; + return *this; + } + + const basic_path_node* parent() const { return parent_;} + + path_node_kind node_kind() const + { + return node_kind_; + } + + string_view_type name() const + { + return name_; + } + + std::size_t size() const + { + return size_; + } + + std::size_t index() const + { + return index_; + } + + void swap(basic_path_node& node) + { + std::swap(parent_, node.parent_); + std::swap(node_kind_, node.node_kind_); + std::swap(name_, node.name_); + std::swap(index_, node.index_); + } + + private: + + std::size_t node_hash() const + { + std::size_t h = node_kind_ == path_node_kind::index ? std::hash{}(index_) : std::hash{}(name_); + + return h; + } + int compare_node(const basic_path_node& other) const + { + int diff = 0; + if (node_kind_ != other.node_kind_) + { + diff = static_cast(node_kind_) - static_cast(other.node_kind_); + } + else + { + switch (node_kind_) + { + case path_node_kind::root: + case path_node_kind::name: + diff = name_.compare(other.name_); + break; + case path_node_kind::index: + diff = index_ < other.index_ ? -1 : index_ > other.index_ ? 1 : 0; + break; + default: + break; + } + } + return diff; + } + + friend bool operator<(const basic_path_node& lhs, const basic_path_node& rhs) + { + std::size_t len = (std::min)(lhs.size(),rhs.size()); + + const basic_path_node* p_lhs = std::addressof(lhs); + const basic_path_node* p_rhs = std::addressof(rhs); + + bool is_less = false; + while (p_lhs->size() > len) + { + p_lhs = p_lhs->parent_; + is_less = false; + } + while (p_rhs->size() > len) + { + p_rhs = p_rhs->parent_; + is_less = true; + } + while (p_lhs != nullptr) + { + int diff = 0; + if (p_lhs->node_kind_ != p_rhs->node_kind_) + { + diff = static_cast(p_lhs->node_kind_) - static_cast(p_rhs->node_kind_); + } + else + { + switch (p_lhs->node_kind_) + { + case path_node_kind::root: + case path_node_kind::name: + diff = p_lhs->name_.compare(p_rhs->name_); + break; + case path_node_kind::index: + diff = static_cast(p_lhs->index_) - static_cast(p_rhs->index_); + break; + default: + break; + } + } + if (diff < 0) + { + is_less = true; + } + else if (diff > 0) + { + is_less = false; + } + + p_lhs = p_lhs->parent_; + p_rhs = p_rhs->parent_; + } + + return is_less; + } + + friend bool operator==(const basic_path_node& lhs, const basic_path_node& rhs) + { + if (lhs.size() != rhs.size()) + { + return false; + } + + const basic_path_node* p_lhs = std::addressof(lhs); + const basic_path_node* p_rhs = std::addressof(rhs); + + bool is_equal = true; + while (p_lhs != nullptr && is_equal) + { + if (p_lhs->node_kind_ != p_rhs->node_kind_) + { + is_equal = false; + } + else + { + switch (p_lhs->node_kind_) + { + case path_node_kind::root: + case path_node_kind::name: + is_equal = p_lhs->name_ == p_rhs->name_; + break; + case path_node_kind::index: + is_equal = p_lhs->index_ == p_rhs->index_; + break; + default: + break; + } + } + p_lhs = p_lhs->parent_; + p_rhs = p_rhs->parent_; + } + + return is_equal; + } + }; + + template + Json* select(Json& root, const basic_path_node& path) + { + using path_node_type = basic_path_node; + + std::vector nodes(path.size(), nullptr); + std::size_t len = nodes.size(); + const path_node_type* p = std::addressof(path); + while (p != nullptr) + { + nodes[--len] = p; + p = p->parent(); + } + while (p != nullptr); + + Json* current = std::addressof(root); + for (auto node : nodes) + { + if (node->node_kind() == path_node_kind::index) + { + if (current->type() != json_type::array_value || node->index() >= current->size()) + { + return nullptr; + } + current = std::addressof(current->at(node->index())); + } + else if (node->node_kind() == path_node_kind::name) + { + if (current->type() != json_type::object_value) + { + return nullptr; + } + auto it = current->find(node->name()); + if (it == current->object_range().end()) + { + return nullptr; + } + current = std::addressof(it->value()); + } + } + return current; + } + + template > + std::basic_string,Allocator> to_basic_string(const basic_path_node& path, const Allocator& alloc=Allocator()) + { + std::basic_string,Allocator> buffer(alloc); + + using path_node_type = basic_path_node; + + std::vector nodes(path.size(), nullptr); + std::size_t len = nodes.size(); + const path_node_type* p = std::addressof(path); + while (p != nullptr) + { + nodes[--len] = p; + p = p->parent(); + } + while (p != nullptr); + + for (auto node : nodes) + { + switch (node->node_kind()) + { + case path_node_kind::root: + buffer.push_back('$'); + break; + case path_node_kind::name: + buffer.push_back('['); + buffer.push_back('\''); + jsoncons::jsonpath::escape_string(node->name().data(), node->name().size(), buffer); + buffer.push_back('\''); + buffer.push_back(']'); + break; + case path_node_kind::index: + buffer.push_back('['); + jsoncons::detail::from_integer(node->index(), buffer); + buffer.push_back(']'); + break; + } + } + + return buffer; + } + + using path_node = basic_path_node; + using wpath_node = basic_path_node; + + inline + std::string to_string(const path_node& path) + { + return to_basic_string(path); + } + + inline + std::wstring to_wstring(const wpath_node& path) + { + return to_basic_string(path); + } + +} // namespace jsonpath +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpointer/jsonpointer.hpp b/third_party/jsoncons_ext/jsonpointer/jsonpointer.hpp new file mode 100644 index 0000000000..0827a91962 --- /dev/null +++ b/third_party/jsoncons_ext/jsonpointer/jsonpointer.hpp @@ -0,0 +1,1442 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPOINTER_JSONPOINTER_HPP +#define JSONCONS_JSONPOINTER_JSONPOINTER_HPP + +#include +#include +#include +#include +#include +#include // std::move +#include // system_error +#include // std::enable_if, std::true_type +#include +#include +#include + +namespace jsoncons { namespace jsonpointer { + + namespace detail { + + enum class pointer_state + { + start, + escaped, + new_token, + part + }; + + } // namespace detail + + template + std::basic_string escape_string(const std::basic_string& s) + { + std::basic_string result; + for (auto c : s) + { + switch (c) + { + case '~': + result.push_back('~'); + result.push_back('0'); + break; + case '/': + result.push_back('~'); + result.push_back('1'); + break; + default: + result.push_back(c); + break; + } + } + return result; + } + + // basic_json_pointer + + template + class basic_json_pointer + { + public: + // Member types + using char_type = CharT; + using string_type = std::basic_string; + using string_view_type = jsoncons::basic_string_view; + using const_iterator = typename std::vector::const_iterator; + using iterator = const_iterator; + using const_reverse_iterator = typename std::vector::const_reverse_iterator; + using reverse_iterator = const_reverse_iterator; + private: + std::vector tokens_; + public: + // Constructors + basic_json_pointer() + { + } + + basic_json_pointer(const std::vector& tokens) + : tokens_(tokens) + { + } + + basic_json_pointer(std::vector&& tokens) + : tokens_(std::move(tokens)) + { + } + + explicit basic_json_pointer(const string_view_type& s) + { + std::error_code ec; + auto jp = parse(s, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + tokens_ = std::move(jp.tokens_); + } + + explicit basic_json_pointer(const string_view_type& s, std::error_code& ec) + { + auto jp = parse(s, ec); + if (!ec) + { + tokens_ = std::move(jp.tokens_); + } + } + + basic_json_pointer(const basic_json_pointer&) = default; + + basic_json_pointer(basic_json_pointer&&) = default; + + static basic_json_pointer parse(const string_view_type& input, std::error_code& ec) + { + std::vector tokens; + if (input.empty()) + { + return basic_json_pointer(); + } + + const char_type* p = input.data(); + const char_type* pend = input.data() + input.size(); + string_type unescaped; + + auto state = jsonpointer::detail::pointer_state::start; + string_type buffer; + + while (p < pend) + { + switch (state) + { + case jsonpointer::detail::pointer_state::start: + switch (*p) + { + case '/': + state = jsonpointer::detail::pointer_state::new_token; + break; + default: + ec = jsonpointer_errc::expected_slash; + return basic_json_pointer(); + }; + break; + case jsonpointer::detail::pointer_state::part: + state = jsonpointer::detail::pointer_state::new_token; + JSONCONS_FALLTHROUGH; + + case jsonpointer::detail::pointer_state::new_token: + switch (*p) + { + case '/': + tokens.push_back(buffer); + buffer.clear(); + state = jsonpointer::detail::pointer_state::part; + break; + case '~': + state = jsonpointer::detail::pointer_state::escaped; + break; + default: + buffer.push_back(*p); + break; + }; + break; + case jsonpointer::detail::pointer_state::escaped: + switch (*p) + { + case '0': + buffer.push_back('~'); + state = jsonpointer::detail::pointer_state::new_token; + break; + case '1': + buffer.push_back('/'); + state = jsonpointer::detail::pointer_state::new_token; + break; + default: + ec = jsonpointer_errc::expected_0_or_1; + return basic_json_pointer(); + }; + break; + } + ++p; + } + if (state == jsonpointer::detail::pointer_state::escaped) + { + ec = jsonpointer_errc::expected_0_or_1; + return basic_json_pointer(); + } + if (state == jsonpointer::detail::pointer_state::new_token || state == jsonpointer::detail::pointer_state::part) + { + tokens.push_back(buffer); + } + return basic_json_pointer(tokens); + } + + // operator= + basic_json_pointer& operator=(const basic_json_pointer&) = default; + + basic_json_pointer& operator=(basic_json_pointer&&) = default; + + // Modifiers + + void clear() + { + tokens_.clear(); + } + + basic_json_pointer& append(const string_type& s) + { + tokens_.push_back(s); + return *this; + } + + template + typename std::enable_if::value, basic_json_pointer&>::type + append(IntegerType val) + { + string_type s; + jsoncons::detail::from_integer(val, s); + tokens_.push_back(s); + + return *this; + } + + basic_json_pointer& operator/=(const string_type& s) + { + tokens_.push_back(s); + return *this; + } + + template + typename std::enable_if::value, basic_json_pointer&>::type + operator/=(IntegerType val) + { + string_type s; + jsoncons::detail::from_integer(val, s); + tokens_.push_back(s); + + return *this; + } + + basic_json_pointer& operator+=(const basic_json_pointer& p) + { + for (const auto& s : p.tokens_) + { + tokens_.push_back(s); + } + return *this; + } + + // Accessors + bool empty() const + { + return tokens_.empty(); + } + + string_type string() const + { + return to_string(); + } + + string_type to_string() const + { + string_type buffer; + for (const auto& token : tokens_) + { + buffer.push_back('/'); + for (auto c : token) + { + switch (c) + { + case '~': + buffer.push_back('~'); + buffer.push_back('0'); + break; + case '/': + buffer.push_back('~'); + buffer.push_back('1'); + break; + default: + buffer.push_back(c); + break; + } + } + } + return buffer; + } + + // Iterators + iterator begin() const + { + return tokens_.begin(); + } + iterator end() const + { + return tokens_.end(); + } + + reverse_iterator rbegin() const + { + return tokens_.rbegin(); + } + reverse_iterator rend() const + { + return tokens_.rend(); + } + + // Non-member functions + friend basic_json_pointer operator/(const basic_json_pointer& lhs, const string_type& rhs) + { + basic_json_pointer p(lhs); + p /= rhs; + return p; + } + + friend basic_json_pointer operator+( const basic_json_pointer& lhs, const basic_json_pointer& rhs ) + { + basic_json_pointer p(lhs); + p += rhs; + return p; + } + + friend bool operator==( const basic_json_pointer& lhs, const basic_json_pointer& rhs ) + { + return lhs.tokens_ == rhs.tokens_; + } + + friend bool operator!=( const basic_json_pointer& lhs, const basic_json_pointer& rhs ) + { + return lhs.tokens_ != rhs.tokens_; + } + + friend std::basic_ostream& + operator<<( std::basic_ostream& os, const basic_json_pointer& p ) + { + os << p.to_string(); + return os; + } + }; + + template + typename std::enable_if::value, basic_json_pointer>::type + operator/(const basic_json_pointer& lhs, IntegerType rhs) + { + basic_json_pointer p(lhs); + p /= rhs; + return p; + } + + using json_pointer = basic_json_pointer; + using wjson_pointer = basic_json_pointer; + + inline + std::string to_string(const json_pointer& ptr) + { + return ptr.to_string(); + } + + inline + std::wstring to_wstring(const wjson_pointer& ptr) + { + return ptr.to_string(); + } + + namespace detail { + + template + const Json* resolve(const Json* current, const typename Json::string_view_type& buffer, std::error_code& ec) + { + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + std::size_t index{0}; + auto result = jsoncons::detail::decimal_to_integer(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return current; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + current = std::addressof(current->at(index)); + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + ec = jsonpointer_errc::key_not_found; + return current; + } + current = std::addressof(current->at(buffer)); + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return current; + } + return current; + } + + template + Json* resolve(Json* current, const typename Json::string_view_type& buffer, bool create_if_missing, std::error_code& ec) + { + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + std::size_t index{0}; + auto result = jsoncons::detail::decimal_to_integer(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return current; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return current; + } + current = std::addressof(current->at(index)); + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + if (create_if_missing) + { + auto r = current->try_emplace(buffer, Json()); + current = std::addressof(r.first->value()); + } + else + { + ec = jsonpointer_errc::key_not_found; + return current; + } + } + else + { + current = std::addressof(current->at(buffer)); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return current; + } + return current; + } + + } // namespace detail + + // get + + template + Json& get(Json& root, + const basic_json_pointer& location, + bool create_if_missing, + std::error_code& ec) + { + if (location.empty()) + { + return root; + } + + Json* current = std::addressof(root); + auto it = location.begin(); + auto end = location.end(); + while (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, *it, create_if_missing, ec); + if (ec) + return *current; + ++it; + } + return *current; + } + + template + typename std::enable_if>::value,Json&>::type + get(Json& root, + const StringSource& location_str, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer::parse(location_str, ec); + if (ec) + { + return root; + } + return get(root, jsonptr, create_if_missing, ec); + } + + template + const Json& get(const Json& root, + const basic_json_pointer& location, + std::error_code& ec) + { + if (location.empty()) + { + return root; + } + + const Json* current = std::addressof(root); + auto it = location.begin(); + auto end = location.end(); + while (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, *it, ec); + if (ec) + return *current; + ++it; + } + return *current; + } + + template + typename std::enable_if>::value,const Json&>::type + get(const Json& root, + const StringSource& location_str, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer::parse(location_str, ec); + if (ec) + { + return root; + } + return get(root, jsonptr, ec); + } + + template + Json& get(Json& root, + const basic_json_pointer& location, + std::error_code& ec) + { + return get(root, location, false, ec); + } + + template + typename std::enable_if>::value,Json&>::type + get(Json& root, + const StringSource& location_str, + std::error_code& ec) + { + return get(root, location_str, false, ec); + } + + template + Json& get(Json& root, + const basic_json_pointer& location, + bool create_if_missing = false) + { + std::error_code ec; + Json& j = get(root, location, create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return j; + } + + template + typename std::enable_if>::value,Json&>::type + get(Json& root, + const StringSource& location_str, + bool create_if_missing = false) + { + std::error_code ec; + Json& result = get(root, location_str, create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return result; + } + + template + const Json& get(const Json& root, const basic_json_pointer& location) + { + std::error_code ec; + const Json& j = get(root, location, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return j; + } + + template + typename std::enable_if>::value,const Json&>::type + get(const Json& root, const StringSource& location_str) + { + std::error_code ec; + const Json& j = get(root, location_str, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + return j; + } + + // contains + + template + bool contains(const Json& root, const basic_json_pointer& location) + { + std::error_code ec; + get(root, location, ec); + return !ec ? true : false; + } + + template + typename std::enable_if>::value,bool>::type + contains(const Json& root, const StringSource& location_str) + { + std::error_code ec; + get(root, location_str, ec); + return !ec ? true : false; + } + + template + void add(Json& root, + const basic_json_pointer& location, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string buffer; + auto it = location.begin(); + auto end = location.end(); + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, create_if_missing, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + current->emplace_back(std::forward(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + std::size_t index{0}; + auto result = jsoncons::detail::decimal_to_integer(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index > current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + if (index == current->size()) + { + current->emplace_back(std::forward(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + auto it2 = current->insert(current->array_range().begin()+index,std::forward(value)); + current = std::addressof(*it2); + } + } + } + else if (current->is_object()) + { + auto r = current->insert_or_assign(buffer,std::forward(value)); + current = std::addressof(r.first->value()); + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + // add + template + typename std::enable_if>::value,void>::type + add(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer::parse(location_str, ec); + if (ec) + { + return; + } + add(root, jsonptr, std::forward(value), create_if_missing, ec); + } + + template + void add(Json& root, + const basic_json_pointer& location, + T&& value, + std::error_code& ec) + { + add(root, location, std::forward(value), false, ec); + } + + template + typename std::enable_if>::value,void>::type + add(Json& root, + const StringSource& location_str, + T&& value, + std::error_code& ec) + { + add(root, location_str, std::forward(value), false, ec); + } + + template + void add(Json& root, + const basic_json_pointer& location, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add(root, location, std::forward(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template + typename std::enable_if>::value,void>::type + add(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add(root, location_str, std::forward(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + // add_if_absent + + template + void add_if_absent(Json& root, + const basic_json_pointer& location, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string buffer; + auto it = location.begin(); + auto end = location.end(); + + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, create_if_missing, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + current->emplace_back(std::forward(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + std::size_t index{0}; + auto result = jsoncons::detail::decimal_to_integer(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index > current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + if (index == current->size()) + { + current->emplace_back(std::forward(value)); + current = std::addressof(current->at(current->size()-1)); + } + else + { + auto it2 = current->insert(current->array_range().begin()+index,std::forward(value)); + current = std::addressof(*it2); + } + } + } + else if (current->is_object()) + { + if (current->contains(buffer)) + { + ec = jsonpointer_errc::key_already_exists; + return; + } + else + { + auto r = current->try_emplace(buffer,std::forward(value)); + current = std::addressof(r.first->value()); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + template + typename std::enable_if>::value,void>::type + add_if_absent(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer::parse(location_str, ec); + if (ec) + { + return; + } + add_if_absent(root, jsonptr, std::forward(value), create_if_missing, ec); + } + + template + typename std::enable_if>::value,void>::type + add_if_absent(Json& root, + const StringSource& location, + T&& value, + std::error_code& ec) + { + add_if_absent(root, location, std::forward(value), false, ec); + } + + template + typename std::enable_if>::value,void>::type + add_if_absent(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add_if_absent(root, location_str, std::forward(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template + void add_if_absent(Json& root, + const basic_json_pointer& location, + T&& value, + std::error_code& ec) + { + add_if_absent(root, location, std::forward(value), false, ec); + } + + template + void add_if_absent(Json& root, + const basic_json_pointer& location, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + add_if_absent(root, location, std::forward(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + // remove + + template + void remove(Json& root, const basic_json_pointer& location, std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string buffer; + auto it = location.begin(); + auto end = location.end(); + + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, false, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + else + { + std::size_t index{0}; + auto result = jsoncons::detail::decimal_to_integer(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + current->erase(current->array_range().begin()+index); + } + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + ec = jsonpointer_errc::key_not_found; + return; + } + else + { + current->erase(buffer); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + template + typename std::enable_if>::value,void>::type + remove(Json& root, const StringSource& location_str, std::error_code& ec) + { + auto jsonptr = basic_json_pointer::parse(location_str, ec); + if (ec) + { + return; + } + remove(root, jsonptr, ec); + } + + template + typename std::enable_if>::value,void>::type + remove(Json& root, const StringSource& location_str) + { + std::error_code ec; + remove(root, location_str, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template + void remove(Json& root, const basic_json_pointer& location) + { + std::error_code ec; + remove(root, location, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + // replace + + template + void replace(Json& root, + const basic_json_pointer& location, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + Json* current = std::addressof(root); + + std::basic_string buffer; + auto it = location.begin(); + auto end = location.end(); + + while (it != end) + { + buffer = *it; + ++it; + if (it != end) + { + current = jsoncons::jsonpointer::detail::resolve(current, buffer, create_if_missing, ec); + if (ec) + return; + } + } + if (current->is_array()) + { + if (buffer.size() == 1 && buffer[0] == '-') + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + else + { + std::size_t index{}; + auto result = jsoncons::detail::decimal_to_integer(buffer.data(), buffer.length(), index); + if (!result) + { + ec = jsonpointer_errc::invalid_index; + return; + } + if (index >= current->size()) + { + ec = jsonpointer_errc::index_exceeds_array_size; + return; + } + current->at(index) = std::forward(value); + } + } + else if (current->is_object()) + { + if (!current->contains(buffer)) + { + if (create_if_missing) + { + current->try_emplace(buffer,std::forward(value)); + } + else + { + ec = jsonpointer_errc::key_not_found; + return; + } + } + else + { + auto r = current->insert_or_assign(buffer,std::forward(value)); + current = std::addressof(r.first->value()); + } + } + else + { + ec = jsonpointer_errc::expected_object_or_array; + return; + } + } + + template + typename std::enable_if>::value,void>::type + replace(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing, + std::error_code& ec) + { + auto jsonptr = basic_json_pointer::parse(location_str, ec); + if (ec) + { + return; + } + replace(root, jsonptr, std::forward(value), create_if_missing, ec); + } + + template + typename std::enable_if>::value,void>::type + replace(Json& root, + const StringSource& location_str, + T&& value, + std::error_code& ec) + { + replace(root, location_str, std::forward(value), false, ec); + } + + template + typename std::enable_if>::value,void>::type + replace(Json& root, + const StringSource& location_str, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + replace(root, location_str, std::forward(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template + void replace(Json& root, + const basic_json_pointer& location, + T&& value, + std::error_code& ec) + { + replace(root, location, std::forward(value), false, ec); + } + + template + void replace(Json& root, + const basic_json_pointer& location, + T&& value, + bool create_if_missing = false) + { + std::error_code ec; + replace(root, location, std::forward(value), create_if_missing, ec); + if (ec) + { + JSONCONS_THROW(jsonpointer_error(ec)); + } + } + + template + typename std::enable_if::value>::type + escape(const String& s, Result& result) + { + for (auto c : s) + { + if (c == '~') + { + result.push_back('~'); + result.push_back('0'); + } + else if (c == '/') + { + result.push_back('~'); + result.push_back('1'); + } + else + { + result.push_back(c); + } + } + } + + template + std::basic_string escape(const jsoncons::basic_string_view& s) + { + std::basic_string result; + + for (auto c : s) + { + if (c == '~') + { + result.push_back('~'); + result.push_back('0'); + } + else if (c == '/') + { + result.push_back('~'); + result.push_back('1'); + } + else + { + result.push_back(c); + } + } + return result; + } + + // flatten + + template + void flatten_(const std::basic_string& parent_key, + const Json& parent_value, + Json& result) + { + using char_type = typename Json::char_type; + using string_type = std::basic_string; + + switch (parent_value.type()) + { + case json_type::array_value: + { + if (parent_value.empty()) + { + // Flatten empty array to null + //result.try_emplace(parent_key, null_type{}); + //result[parent_key] = parent_value; + result.try_emplace(parent_key, parent_value); + } + else + { + for (std::size_t i = 0; i < parent_value.size(); ++i) + { + string_type key(parent_key); + key.push_back('/'); + jsoncons::detail::from_integer(i,key); + flatten_(key, parent_value.at(i), result); + } + } + break; + } + + case json_type::object_value: + { + if (parent_value.empty()) + { + // Flatten empty object to null + //result.try_emplace(parent_key, null_type{}); + //result[parent_key] = parent_value; + result.try_emplace(parent_key, parent_value); + } + else + { + for (const auto& item : parent_value.object_range()) + { + string_type key(parent_key); + key.push_back('/'); + escape(jsoncons::basic_string_view(item.key().data(),item.key().size()), key); + flatten_(key, item.value(), result); + } + } + break; + } + + default: + { + // add primitive parent_value with its reference string + //result[parent_key] = parent_value; + result.try_emplace(parent_key, parent_value); + break; + } + } + } + + template + Json flatten(const Json& value) + { + Json result; + std::basic_string parent_key; + flatten_(parent_key, value, result); + return result; + } + + + // unflatten + + enum class unflatten_options {none,assume_object = 1 +}; + + template + Json safe_unflatten (Json& value) + { + if (!value.is_object() || value.empty()) + { + return value; + } + bool safe = true; + std::size_t index = 0; + for (const auto& item : value.object_range()) + { + std::size_t n; + auto r = jsoncons::detail::decimal_to_integer(item.key().data(),item.key().size(), n); + if (!r || (index++ != n)) + { + safe = false; + break; + } + } + + if (safe) + { + Json j(json_array_arg); + j.reserve(value.size()); + for (auto& item : value.object_range()) + { + j.emplace_back(std::move(item.value())); + } + Json a(json_array_arg); + for (auto& item : j.array_range()) + { + a.emplace_back(safe_unflatten (item)); + } + return a; + } + else + { + Json o(json_object_arg); + for (auto& item : value.object_range()) + { + o.try_emplace(item.key(), safe_unflatten (item.value())); + } + return o; + } + } + + template + jsoncons::optional try_unflatten_array(const Json& value) + { + using char_type = typename Json::char_type; + + if (JSONCONS_UNLIKELY(!value.is_object())) + { + JSONCONS_THROW(jsonpointer_error(jsonpointer_errc::argument_to_unflatten_invalid)); + } + Json result; + + for (const auto& item: value.object_range()) + { + Json* part = &result; + basic_json_pointer ptr(item.key()); + std::size_t index = 0; + for (auto it = ptr.begin(); it != ptr.end(); ) + { + auto s = *it; + size_t n{0}; + auto r = jsoncons::detail::decimal_to_integer(s.data(), s.size(), n); + if (r.ec == jsoncons::detail::to_integer_errc() && (index++ == n)) + { + if (!part->is_array()) + { + *part = Json(json_array_arg); + } + if (++it != ptr.end()) + { + if (n+1 > part->size()) + { + Json& ref = part->emplace_back(); + part = std::addressof(ref); + } + else + { + part = &part->at(n); + } + } + else + { + Json& ref = part->emplace_back(item.value()); + part = std::addressof(ref); + } + } + else if (part->is_object()) + { + if (++it != ptr.end()) + { + auto res = part->try_emplace(s,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(s, item.value()); + part = &(res.first->value()); + } + } + else + { + return jsoncons::optional(); + } + } + } + + return result; + } + + template + Json unflatten_to_object(const Json& value, unflatten_options options = unflatten_options::none) + { + using char_type = typename Json::char_type; + + if (JSONCONS_UNLIKELY(!value.is_object())) + { + JSONCONS_THROW(jsonpointer_error(jsonpointer_errc::argument_to_unflatten_invalid)); + } + Json result; + + for (const auto& item: value.object_range()) + { + Json* part = &result; + basic_json_pointer ptr(item.key()); + for (auto it = ptr.begin(); it != ptr.end(); ) + { + auto s = *it; + if (++it != ptr.end()) + { + auto res = part->try_emplace(s,Json()); + part = &(res.first->value()); + } + else + { + auto res = part->try_emplace(s, item.value()); + part = &(res.first->value()); + } + } + } + + return options == unflatten_options::none ? safe_unflatten (result) : result; + } + + template + Json unflatten(const Json& value, unflatten_options options = unflatten_options::none) + { + if (options == unflatten_options::none) + { + jsoncons::optional j = try_unflatten_array(value); + return j ? *j : unflatten_to_object(value,options); + } + else + { + return unflatten_to_object(value,options); + } + } + +} // namespace jsonpointer +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/jsonpointer/jsonpointer_error.hpp b/third_party/jsoncons_ext/jsonpointer/jsonpointer_error.hpp new file mode 100644 index 0000000000..65febe4fed --- /dev/null +++ b/third_party/jsoncons_ext/jsonpointer/jsonpointer_error.hpp @@ -0,0 +1,119 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONPOINTER_JSONPOINTER_ERROR_HPP +#define JSONCONS_JSONPOINTER_JSONPOINTER_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace jsonpointer { + +class jsonpointer_error : public std::system_error, public virtual json_exception +{ +public: + jsonpointer_error(const std::error_code& ec) + : std::system_error(ec) + { + } + jsonpointer_error(const std::error_code& ec, const std::string& what_arg) + : std::system_error(ec, what_arg) + { + } + jsonpointer_error(const std::error_code& ec, const char* what_arg) + : std::system_error(ec, what_arg) + { + } + jsonpointer_error(const jsonpointer_error& other) = default; + + jsonpointer_error(jsonpointer_error&& other) = default; + + const char* what() const noexcept override + { + return std::system_error::what(); + } +}; + +enum class jsonpointer_errc +{ + success = 0, + expected_slash = 1, + index_exceeds_array_size, + expected_0_or_1, + invalid_index, + key_not_found, + key_already_exists, + expected_object_or_array, + end_of_input, + unexpected_end_of_input, + argument_to_unflatten_invalid, + invalid_flattened_key, + invalid_uri_escaped_data +}; + +class jsonpointer_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/jsonpointer"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case jsonpointer_errc::expected_slash: + return "Expected /"; + case jsonpointer_errc::index_exceeds_array_size: + return "Index exceeds array size"; + case jsonpointer_errc::expected_0_or_1: + return "Expected '0' or '1' after escape character '~'"; + case jsonpointer_errc::key_not_found: + return "Key not found"; + case jsonpointer_errc::invalid_index: + return "Invalid array index"; + case jsonpointer_errc::key_already_exists: + return "Key already exists"; + case jsonpointer_errc::expected_object_or_array: + return "Expected object or array"; + case jsonpointer_errc::end_of_input: + return "Unexpected end of input"; + case jsonpointer_errc::unexpected_end_of_input: + return "Unexpected end of jsonpointer input"; + case jsonpointer_errc::argument_to_unflatten_invalid: + return "Argument to unflatten must be an object"; + case jsonpointer_errc::invalid_flattened_key: + return "Flattened key is invalid"; + default: + return "Unknown jsonpointer error"; + } + } +}; + +inline +const std::error_category& jsonpointer_error_category() +{ + static jsonpointer_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(jsonpointer_errc result) +{ + return std::error_code(static_cast(result),jsonpointer_error_category()); +} + +}} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons_ext/jsonschema/common/compilation_context.hpp b/third_party/jsoncons_ext/jsonschema/common/compilation_context.hpp new file mode 100644 index 0000000000..d31c45f17e --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/compilation_context.hpp @@ -0,0 +1,97 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_COMPILATION_CONTEXT_HPP +#define JSONCONS_JSONSCHEMA_COMMON_COMPILATION_CONTEXT_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + class compilation_context + { + uri_wrapper base_uri_; + std::vector uris_; + jsoncons::optional id_; + public: + + compilation_context() + { + } + + explicit compilation_context(const uri_wrapper& retrieval_uri) + : base_uri_(retrieval_uri), + uris_(std::vector{{retrieval_uri}}) + { + } + + explicit compilation_context(const std::vector& uris) + : uris_(uris) + { + if (uris_.empty()) + { + uris_.push_back(uri_wrapper{"#"}); + } + base_uri_ = uris_.back(); + } + + explicit compilation_context(const std::vector& uris, const jsoncons::optional& id) + : uris_(uris), id_(id) + { + if (uris_.empty()) + { + uris_.push_back(uri_wrapper{"#"}); + } + base_uri_ = uris_.back(); + } + + const std::vector& uris() const {return uris_;} + + const jsoncons::optional& id() const + { + return id_; + } + + uri get_base_uri() const + { + return base_uri_.uri(); + } + + jsoncons::uri make_schema_location(const std::string& keyword) const + { + for (auto it = uris_.rbegin(); it != uris_.rend(); ++it) + { + if (!it->has_plain_name_fragment()) + { + return it->append(keyword).uri(); + } + } + return uri{"#"}; + } + + static jsoncons::uri make_random_uri() + { + std::random_device dev; + std::mt19937 gen{ dev() }; + std::uniform_int_distribution dist(1, 10000); + + std::string str = "https:://jsoncons.com/" + std::to_string(dist(gen)); + jsoncons::uri uri{str}; + return uri; + } + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_COMPILATION_CONTEXT_HPP diff --git a/third_party/jsoncons_ext/jsonschema/common/evaluation_context.hpp b/third_party/jsoncons_ext/jsonschema/common/evaluation_context.hpp new file mode 100644 index 0000000000..9b678ff767 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/evaluation_context.hpp @@ -0,0 +1,164 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_EVALUATION_CONTEXT_HPP +#define JSONCONS_JSONSCHEMA_COMMON_EVALUATION_CONTEXT_HPP + +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + template + class schema_validator; + + enum class evaluation_flags : uint32_t {require_evaluated_properties=1, require_evaluated_items=2}; + + inline evaluation_flags operator~(evaluation_flags a) + { + return static_cast(~static_cast(a)); + } + + inline evaluation_flags operator&(evaluation_flags a, evaluation_flags b) + { + return static_cast(static_cast(a) & static_cast(b)); + } + + inline evaluation_flags operator^(evaluation_flags a, evaluation_flags b) + { + return static_cast(static_cast(a) ^ static_cast(b)); + } + + inline evaluation_flags operator|(evaluation_flags a, evaluation_flags b) + { + return static_cast(static_cast(a) | static_cast(b)); + } + + inline evaluation_flags operator&=(evaluation_flags& a, evaluation_flags b) + { + a = a & b; + return a; + } + + inline evaluation_flags operator^=(evaluation_flags& a, evaluation_flags b) + { + a = a ^ b; + return a; + } + + inline evaluation_flags operator|=(evaluation_flags& a, evaluation_flags b) + { + a = a | b; + return a; + } + + template + class evaluation_context + { + private: + std::vector*> dynamic_scope_; + jsonpointer::json_pointer eval_path_; + evaluation_flags flags_; + public: + evaluation_context() + : flags_{} + { + } + + evaluation_context(const evaluation_context& other) + : dynamic_scope_ { other.dynamic_scope_}, eval_path_{other.eval_path_}, + flags_(other.flags_) + { + } + + evaluation_context(evaluation_context&& other) + : dynamic_scope_{std::move(other.dynamic_scope_)},eval_path_{std::move(other.eval_path_)}, + flags_(other.flags_) + { + } + + evaluation_context(const evaluation_context& parent, const schema_validator *validator) + : dynamic_scope_ { parent.dynamic_scope_ }, eval_path_{ parent.eval_path_ }, + flags_(parent.flags_) + { + if (validator->id() || dynamic_scope_.empty()) + { + dynamic_scope_.push_back(validator); + } + } + + evaluation_context(const evaluation_context& parent, const schema_validator *validator, + evaluation_flags flags) + : dynamic_scope_ { parent.dynamic_scope_ }, eval_path_{ parent.eval_path_ }, + flags_(flags) + { + if (validator->id() || dynamic_scope_.empty()) + { + dynamic_scope_.push_back(validator); + } + } + + evaluation_context(const evaluation_context& parent, const std::string& name) + : dynamic_scope_{parent.dynamic_scope_}, eval_path_(parent.eval_path() / name), + flags_(parent.flags_) + + { + } + + evaluation_context(const evaluation_context& parent, const std::string& name, + evaluation_flags flags) + : dynamic_scope_{parent.dynamic_scope_}, eval_path_(parent.eval_path() / name), + flags_(flags) + { + } + + evaluation_context(const evaluation_context& parent, std::size_t index) + : dynamic_scope_{parent.dynamic_scope_}, eval_path_(parent.eval_path() / index), + flags_(parent.flags_) + { + } + + evaluation_context(const evaluation_context& parent, std::size_t index, + evaluation_flags flags) + : dynamic_scope_{parent.dynamic_scope_}, eval_path_(parent.eval_path() / index), + flags_(flags) + { + } + + const std::vector*>& dynamic_scope() const + { + return dynamic_scope_; + } + + const jsonpointer::json_pointer& eval_path() const + { + return eval_path_; + } + + evaluation_flags eval_flags() const + { + return flags_; + } + + bool require_evaluated_properties() const + { + return (flags_ & evaluation_flags::require_evaluated_properties) == evaluation_flags::require_evaluated_properties; + } + + bool require_evaluated_items() const + { + return (flags_ & evaluation_flags::require_evaluated_items) == evaluation_flags::require_evaluated_items; + } + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_COMPILATION_CONTEXT_HPP diff --git a/third_party/jsoncons_ext/jsonschema/common/format_validator.hpp b/third_party/jsoncons_ext/jsonschema/common/format_validator.hpp new file mode 100644 index 0000000000..d103c6304b --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/format_validator.hpp @@ -0,0 +1,1187 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_FORMAT_VALIDATOR_HPP +#define JSONCONS_JSONSCHEMA_COMMON_FORMAT_VALIDATOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif + +namespace jsoncons { +namespace jsonschema { + + + inline bool is_digit(char c) + { + return c >= '0' && c <= '9'; + } + + inline bool is_vchar(char c) + { + return c >= 0x21 && c <= 0x7E; + } + + inline bool is_alpha(char c) + { + return (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z'); + } + + inline + bool is_atext( char c) + { + switch (c) + { + case '!': + case '#': + case '$': + case '%': + case '&': + case '\'': + case '*': + case '+': + case '-': + case '/': + case '=': + case '?': + case '^': + case '_': + case '`': + case '{': + case '|': + case '}': + case '~': + return true; + default: + return is_digit(c) || is_alpha(c); + } + } + + inline + bool is_dtext( char c) + { + return (c >= 33 && c <= 90) || (c >= 94 && c <= 126); + } + + + + // RFC 5322, section 3.4.1 + inline + bool validate_email_rfc5322(const std::string& s) + { + enum class state_t {local_part,atom,dot_atom,quoted_string,amp,domain,domain_name,domain_literal,done}; + + state_t state = state_t::local_part; + std::size_t part_length = 0; + + std::size_t length = s.size(); + for (std::size_t i = 0; i < length; ++i) + { + char c = s[i]; + switch (state) + { + case state_t::local_part: + { + if (is_atext(c)) + { + state = state_t::atom; + } + else if (c == '"') + { + state = state_t::quoted_string; + } + else + { + return false; + } + break; + } + case state_t::dot_atom: + { + if (is_atext(c)) + { + ++part_length; + state = state_t::atom; + } + else + return false; + break; + } + case state_t::atom: + { + switch (c) + { + case '@': + state = state_t::domain; + part_length = 0; + break; + case '.': + state = state_t::dot_atom; + ++part_length; + break; + default: + if (is_atext(c)) + ++part_length; + else + return false; + break; + } + break; + } + case state_t::quoted_string: + { + if (c == '\"') + { + state = state_t::amp; + } + else + { + ++part_length; + } + break; + } + case state_t::amp: + { + if (c == '@') + { + state = state_t::domain; + part_length = 0; + } + else + { + return false; + } + break; + } + case state_t::domain: + { + switch (c) + { + case '[': + state = state_t::domain_literal; + break; + default: + if (is_digit(c) || is_alpha(c)) + { + state = state_t::domain_name; + } + else + { + return false; + } + break; + } + break; + } + case state_t::domain_literal: + { + switch (c) + { + case ']': + state = state_t::done; + break; + default: + if (!is_dtext(c)) + { + return false; + } + break; + } + break; + } + case state_t::domain_name: + { + switch (c) + { + case '.': + if (part_length == 0) + { + return false; + } + part_length = 0; + break; + default: + { + if (is_digit(c) || is_alpha(c) || c == '-') + { + ++part_length; + } + else + return false; + } + break; + } + break; + } + default: + break; + } + } + + return state == state_t::domain_name || state == state_t::done; + } + + // RFC 2673, Section 3.2 + + inline + bool validate_ipv6_rfc2373(const std::string& s) + { + enum class state_t{start,expect_hexdig_or_unspecified, + hexdig, decdig,expect_unspecified, unspecified}; + + state_t state = state_t::start; + + std::size_t digit_count = 0; + std::size_t piece_count = 0; + std::size_t piece_count2 = 0; + bool has_unspecified = false; + std::size_t dec_value = 0; + + for (std::size_t i = 0; i < s.length(); ++i) + { + char c = s[i]; + switch (state) + { + case state_t::start: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + state = state_t::hexdig; + ++digit_count; + piece_count = 0; + break; + case ':': + if (!has_unspecified) + { + state = state_t::expect_unspecified; + } + else + { + return false; + } + break; + default: + return false; + } + break; + } + case state_t::expect_hexdig_or_unspecified: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + dec_value = dec_value*10 + static_cast(c - '0'); // just in case this piece is followed by a dot + state = state_t::hexdig; + ++digit_count; + break; + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + state = state_t::hexdig; + ++digit_count; + break; + case ':': + if (!has_unspecified) + { + has_unspecified = true; + state = state_t::unspecified; + } + else + { + return false; + } + break; + default: + return false; + } + break; + } + case state_t::expect_unspecified: + { + if (c == ':') + { + has_unspecified = true; + state = state_t::unspecified; + } + else + { + return false; + } + break; + } + case state_t::hexdig: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + ++digit_count; + break; + case ':': + if (digit_count <= 4) + { + ++piece_count; + digit_count = 0; + dec_value = 0; + state = state_t::expect_hexdig_or_unspecified; + } + else + { + return false; + } + break; + case '.': + if (piece_count == 6 || has_unspecified) + { + ++piece_count2; + state = state_t::decdig; + dec_value = 0; + } + else + { + return false; + } + break; + default: + return false; + } + break; + } + case state_t::decdig: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + dec_value = dec_value*10 + static_cast(c - '0'); + ++digit_count; + break; + case '.': + if (dec_value > 0xff) + { + return false; + } + digit_count = 0; + dec_value = 0; + ++piece_count2; + break; + default: + return false; + } + break; + } + case state_t::unspecified: + { + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + state = state_t::hexdig; + ++digit_count; + break; + default: + return false; + } + break; + } + default: + return false; + } + } + + switch (state) + { + case state_t::unspecified: + return piece_count <= 8; + case state_t::hexdig: + if (digit_count <= 4) + { + ++piece_count; + return digit_count > 0 && (piece_count == 8 || (has_unspecified && piece_count <= 8)); + } + else + { + return false; + } + case state_t::decdig: + ++piece_count2; + if (dec_value > 0xff) + { + return false; + } + return digit_count > 0 && piece_count2 == 4; + default: + return false; + } + } + + // RFC 2673, Section 3.2 + + inline + bool validate_ipv4_rfc2673(const std::string& s) + { + enum class state_t {expect_indicator_or_dotted_quad,decbyte, + bindig, octdig, hexdig}; + + state_t state = state_t::expect_indicator_or_dotted_quad; + + std::size_t digit_count = 0; + std::size_t decbyte_count = 0; + std::size_t value = 0; + + for (std::size_t i = 0; i < s.length(); ++i) + { + char c = s[i]; + switch (state) + { + case state_t::expect_indicator_or_dotted_quad: + { + switch (c) + { + case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + state = state_t::decbyte; + decbyte_count = 0; + digit_count = 1; + value = 0; + break; + case 'b': + state = state_t::bindig; + digit_count = 0; + break; + case '0': + state = state_t::octdig; + digit_count = 0; + break; + case 'x': + state = state_t::hexdig; + digit_count = 0; + break; + default: + return false; + } + break; + } + case state_t::bindig: + { + if (digit_count >= 256) + { + return false; + } + switch (c) + { + case '0':case '1': + ++digit_count; + break; + default: + return false; + } + break; + } + case state_t::octdig: + { + if (digit_count >= 86) + { + return false; + } + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7': + ++digit_count; + break; + default: + return false; + } + break; + } + case state_t::hexdig: + { + if (digit_count >= 64) + { + return false; + } + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + case 'A':case 'B':case 'C':case 'D':case 'E':case 'F': + case 'a':case 'b':case 'c':case 'd':case 'e':case 'f': + ++digit_count; + break; + default: + return false; + } + break; + } + case state_t::decbyte: + { + if (decbyte_count >= 4) + { + return false; + } + switch (c) + { + case '0':case '1':case '2':case '3':case '4':case '5':case '6':case '7':case '8': case '9': + { + if (digit_count >= 3) + { + return false; + } + ++digit_count; + value = value*10 + static_cast(c - '0'); + if (value > 255) + { + return false; + } + break; + } + case '.': + if (decbyte_count > 3) + { + return false; + } + ++decbyte_count; + digit_count = 0; + value = 0; + break; + default: + return false; + } + break; + } + default: + return false; + } + } + + switch (state) + { + case state_t::decbyte: + if (digit_count > 0) + { + ++decbyte_count; + } + else + { + return false; + } + return (decbyte_count == 4) ? true : false; + case state_t::bindig: + return digit_count > 0 ? true : false; + case state_t::octdig: + return digit_count > 0 ? true : false; + case state_t::hexdig: + return digit_count > 0 ? true : false; + default: + return false; + } + } + + // RFC 1034, Section 3.1 + inline + bool validate_hostname_rfc1034(const std::string& hostname) + { + enum class state_t {start_label,expect_letter_or_digit_or_hyphen_or_dot}; + + state_t state = state_t::start_label; + std::size_t length = hostname.length() - 1; + std::size_t label_length = 0; + + for (std::size_t i = 0; i < length; ++i) + { + char c = hostname[i]; + switch (state) + { + case state_t::start_label: + { + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) + { + ++label_length; + state = state_t::expect_letter_or_digit_or_hyphen_or_dot; + } + else + { + return false; + } + break; + } + case state_t::expect_letter_or_digit_or_hyphen_or_dot: + { + if (c == '.') + { + label_length = 0; + state = state_t::start_label; + } + else if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || + (c >= '0' && c < '9') || c == '-')) + { + return false; + } + if (++label_length > 63) + { + return false; + } + break; + } + } + } + + char last = hostname.back(); + if (!((last >= 'a' && last <= 'z') || (last >= 'A' && last <= 'Z') || (last >= '0' && last < '9'))) + { + return false; + } + return true; + } + + inline + bool is_leap_year(std::size_t year) + { + return (year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)); + } + + inline + std::size_t days_in_month(std::size_t year, std::size_t month) + { + switch (month) + { + case 1: return 31; + case 2: return is_leap_year(year) ? 29 : 28; + case 3: return 31; + case 4: return 30; + case 5: return 31; + case 6: return 30; + case 7: return 31; + case 8: return 31; + case 9: return 30; + case 10: return 31; + case 11: return 30; + case 12: return 31; + default: + JSONCONS_UNREACHABLE(); + break; + } + } + + enum class date_time_type {date_time,date,time}; + // RFC 3339, Section 5.6 + inline + bool validate_date_time_rfc3339(const std::string& s, date_time_type type) + { + enum class state_t {fullyear,month,mday,hour,minute,second,secfrac,z,offset_hour,offset_minute}; + + std::size_t piece_length = 0; + std::size_t year = 0; + std::size_t month = 0; + std::size_t mday = 0; + int hour = 0; + int minute = 0; + int second = 0; + int secfrac = 0; + int offset_signum = 0; + int offset_hour = 0; + int offset_minute = 0; + + state_t state = (type == date_time_type::time) ? state_t::hour : state_t::fullyear; + + for (char c : s) + { + switch (state) + { + case state_t::fullyear: + { + if (piece_length < 4 && (c >= '0' && c <= '9')) + { + piece_length++; + year = year*10 + static_cast(c - '0'); + } + else if (c == '-' && piece_length == 4) + { + state = state_t::month; + piece_length = 0; + } + else + { + return false; + } + break; + } + case state_t::month: + { + if (piece_length < 2 && (c >= '0' && c <= '9')) + { + piece_length++; + month = month*10 + static_cast(c - '0'); + } + else if (c == '-' && piece_length == 2 && (month >=1 && month <= 12)) + { + state = state_t::mday; + piece_length = 0; + } + else + { + return false; + } + break; + } + case state_t::mday: + { + if (piece_length < 2 && (c >= '0' && c <= '9')) + { + piece_length++; + mday = mday *10 + static_cast(c - '0'); + } + else if ((c == 'T' || c == 't') && piece_length == 2 && (mday <= days_in_month(year, month))) + { + piece_length = 0; + state = state_t::hour; + } + else + { + return false; + } + break; + } + case state_t::hour: + { + if (piece_length < 2 && (c >= '0' && c <= '9')) + { + piece_length++; + hour = hour*10 + static_cast(c - '0'); + } + else if (c == ':' && piece_length == 2 && (/*hour >=0 && */ hour <= 23)) + { + state = state_t::minute; + piece_length = 0; + } + else + { + return false; + } + break; + } + case state_t::minute: + { + if (piece_length < 2 && (c >= '0' && c <= '9')) + { + piece_length++; + minute = minute*10 + static_cast(c - '0'); + } + else if (c == ':' && piece_length == 2 && (/*minute >=0 && */minute <= 59)) + { + state = state_t::second; + piece_length = 0; + } + else + { + return false; + } + break; + } + case state_t::second: + { + if (piece_length < 2 && (c >= '0' && c <= '9')) + { + piece_length++; + second = second*10 + static_cast(c - '0'); + } + else if (piece_length == 2 && second <= 60) // 00-58, 00-59, 00-60 based on leap second rules + { + switch (c) + { + case '.': + state = state_t::secfrac; + break; + case '+': + offset_signum = 1; + piece_length = 0; + state = state_t::offset_hour; + break; + case '-': + offset_signum = -1; + piece_length = 0; + state = state_t::offset_hour; + break; + case 'Z': + case 'z': + state = state_t::z; + break; + default: + return false; + } + } + break; + } + case state_t::secfrac: + { + if (c >= '0' && c <= '9') + { + secfrac = secfrac*10 + static_cast(c - '0'); + } + else + { + switch (c) + { + case '+': + offset_signum = 1; + piece_length = 0; + state = state_t::offset_hour; + break; + case '-': + offset_signum = -1; + piece_length = 0; + state = state_t::offset_hour; + break; + case 'Z': + case 'z': + state = state_t::z; + break; + default: + return false; + } + } + break; + } + case state_t::offset_hour: + { + if (piece_length < 2 && (c >= '0' && c <= '9')) + { + piece_length++; + offset_hour = offset_hour*10 + static_cast(c - '0'); + } + else if (c == ':' && piece_length == 2 && (/*offset_hour >=0 && */offset_hour <= 23)) + { + piece_length = 0; + state = state_t::offset_minute; + } + else + { + return false; + } + break; + } + case state_t::offset_minute: + { + if (piece_length < 2 && (c >= '0' && c <= '9')) + { + piece_length++; + offset_minute = offset_minute*10 + static_cast(c - '0'); + } + else if (c == ':' && piece_length == 2 && (/*offset_minute >=0 && */offset_minute <= 59)) + { + piece_length = 0; + } + else + { + return false; + } + break; + } + case state_t::z: + return false; // Nothing follows z + default: + return false; + } + } + + switch (state) + { + case state_t::hour: + case state_t::minute: + case state_t::second: + case state_t::secfrac: + return false; + default: + break; + } + + if (offset_hour < 0 || offset_hour > 23) + { + return false; + } + if (offset_minute < 0 || offset_minute > 59) + { + return false; + } + if (offset_signum == -1) + { + offset_hour = -offset_hour; + offset_minute = -offset_minute; + } + auto day_minutes = hour * 60 + minute - (offset_hour * 60 + offset_minute); + if (day_minutes < 0) + day_minutes += 60 * 24; + hour = day_minutes % 24; + minute = day_minutes / 24; + + if (hour == 23 && minute == 59) + { + if (second < 0 || second > 60) + { + return false; + } + } + else + { + if (second < 0 || second > 59) + { + return false; + } + } + + if (type == date_time_type::date) + { + return state == state_t::mday && piece_length == 2 && (mday >= 1 && mday <= days_in_month(year, month)); + } + else + { + return state == state_t::offset_minute || state == state_t::z || state == state_t::secfrac; + } + } + + // format checkers + using format_checker = std::function; + + inline + walk_result uri_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& str, + error_reporter& reporter) + { + std::error_code ec; + uri::parse(str, ec); + if (ec) + { + walk_result result = reporter.error(validation_message("uri", + eval_path, + schema_location, + instance_location, + "'" + str + "' is not a valid URI.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result jsonpointer_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& str, + error_reporter& reporter) + { + std::error_code ec; + jsonpointer::json_pointer::parse(str, ec); + if (ec) + { + walk_result result = reporter.error(validation_message("json-pointer", + eval_path, + schema_location, + instance_location, + "'" + str + "' is not a valid JSONPointer.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + + inline + walk_result rfc3339_date_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& value, + error_reporter& reporter) + { + if (!validate_date_time_rfc3339(value,date_time_type::date)) + { + walk_result result = reporter.error(validation_message("date", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a RFC 3339 date string.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result rfc3339_time_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string &value, + error_reporter& reporter) + { + if (!validate_date_time_rfc3339(value, date_time_type::time)) + { + walk_result result = reporter.error(validation_message("time", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a RFC 3339 time string.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result rfc3339_date_time_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string &value, + error_reporter& reporter) + { + if (!validate_date_time_rfc3339(value, date_time_type::date_time)) + { + walk_result result = reporter.error(validation_message("date-time", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a RFC 3339 date-time string.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result email_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& value, + error_reporter& reporter) + { + if (!validate_email_rfc5322(value)) + { + walk_result result = reporter.error(validation_message("email", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a valid email address as defined by RFC 5322.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result hostname_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& value, + error_reporter& reporter) + { + if (!validate_hostname_rfc1034(value)) + { + walk_result result = reporter.error(validation_message("hostname", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a valid hostname as defined by RFC 3986 Appendix A.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result ipv4_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& value, + error_reporter& reporter) + { + if (!validate_ipv4_rfc2673(value)) + { + walk_result result = reporter.error(validation_message("ipv4", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a valid IPv4 address as defined by RFC 2673.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result ipv6_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& value, + error_reporter& reporter) + { + if (!validate_ipv6_rfc2373(value)) + { + walk_result result = reporter.error(validation_message("ipv6", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a valid IPv6 address as defined by RFC 2373.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + inline + walk_result regex_check(const jsonpointer::json_pointer& eval_path, const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& value, + error_reporter& reporter) + { +#if defined(JSONCONS_HAS_STD_REGEX) + try + { + std::regex re(value, std::regex::ECMAScript); + } + catch (const std::exception& e) + { + walk_result result = reporter.error(validation_message("pattern", + eval_path, + schema_location, + instance_location, + "'" + value + "' is not a valid ECMAScript regular expression. " + e.what())); + if (result == walk_result::abort) + { + return result; + } + } +#endif + return walk_result::advance; + } + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_FORMAT_VALIDATOR_HPP diff --git a/third_party/jsoncons_ext/jsonschema/common/keyword_validators.hpp b/third_party/jsoncons_ext/jsonschema/common/keyword_validators.hpp new file mode 100644 index 0000000000..164797b9ea --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/keyword_validators.hpp @@ -0,0 +1,3968 @@ +// Copyright 2013-2024 Daniel Parker +// +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_KEYWORD_VALIDATORS_HPP +#define JSONCONS_JSONSCHEMA_COMMON_KEYWORD_VALIDATORS_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif + +namespace jsoncons { +namespace jsonschema { + + template + class recursive_ref_validator : public keyword_validator_base, public virtual ref + { + using keyword_validator_type = std::unique_ptr>; + using schema_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + const schema_validator *tentative_target_; + + public: + recursive_ref_validator(const Json& schema, const uri& schema_location) + : keyword_validator_base("$recursiveRef", schema, schema_location) + {} + + uri get_base_uri() const + { + return this->schema_location(); + } + + void set_referred_schema(const schema_validator* target) final { tentative_target_ = target; } + + private: + + walk_result do_validate(const evaluation_context& context, + const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + auto rit = context.dynamic_scope().rbegin(); + auto rend = context.dynamic_scope().rend(); + + const schema_validator* schema_ptr = tentative_target_; + + JSONCONS_ASSERT(schema_ptr != nullptr); + + if (schema_ptr->recursive_anchor()) + { + while (rit != rend) + { + if ((*rit)->recursive_anchor()) + { + schema_ptr = *rit; + } + ++rit; + } + } + + evaluation_context this_context(context, this->keyword_name()); + if (schema_ptr == nullptr) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Unresolved schema reference " + this->schema_location().string())); + return result; + } + + return schema_ptr->validate(this_context, instance, instance_location, results, reporter, patch); + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + auto rit = context.dynamic_scope().rbegin(); + auto rend = context.dynamic_scope().rend(); + + const schema_validator* schema_ptr = tentative_target_; + + JSONCONS_ASSERT(schema_ptr != nullptr); + + if (schema_ptr->recursive_anchor()) + { + while (rit != rend) + { + if ((*rit)->recursive_anchor()) + { + schema_ptr = *rit; + } + ++rit; + } + } + + //std::cout << "recursive_ref_validator.do_validate " << "keywordLocation: << " << this->schema_location().string() << ", instanceLocation:" << instance_location.string() << "\n"; + + if (schema_ptr == nullptr) + { + return walk_result::advance; + } + evaluation_context this_context(context, this->keyword_name()); + return schema_ptr->walk(this_context, instance, instance_location, reporter); + } + }; + + template + class dynamic_ref_validator : public keyword_validator_base, public virtual ref + { + using keyword_validator_type = std::unique_ptr>; + using schema_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + uri_wrapper value_; + const schema_validator* tentative_target_; + + public: + dynamic_ref_validator(const Json& schema, const uri& schema_location, const uri_wrapper& value) + : keyword_validator_base("$dynamicRef", schema, schema_location), value_(value) + { + //std::cout << "dynamic_ref_validator path: " << schema_location.string() << ", value: " << value.string() << "\n"; + } + + void set_referred_schema(const schema_validator* target) final { tentative_target_ = target; } + + const jsoncons::uri& value() const { return value_.uri(); } + + uri get_base_uri() const + { + return this->schema_location(); + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + //std::cout << "dynamic_ref_validator [" << context.eval_path().string() << "," << this->schema_location().string() << "]"; + //std::cout << "results:\n"; + //for (const auto& s : results) + //{ + // std::cout << " " << s << "\n"; + //} + //std::cout << "\n"; + + auto rit = context.dynamic_scope().rbegin(); + auto rend = context.dynamic_scope().rend(); + + //std::cout << "dynamic_ref_validator::do_validate " << this->value().string() << "\n"; + + const schema_validator *schema_ptr = tentative_target_; + + JSONCONS_ASSERT(schema_ptr != nullptr); + + if (value_.has_plain_name_fragment() && schema_ptr->dynamic_anchor()) + { + while (rit != rend) + { + auto p = (*rit)->get_schema_for_dynamic_anchor(schema_ptr->dynamic_anchor()->fragment()); + //std::cout << " (2) [" << (*rit)->schema_location().string() << "] " << ((*rit)->dynamic_anchor() ? (*rit)->dynamic_anchor()->value().string() : "") << "\n"; + + if (p != nullptr) + { + //std::cout << "Match found " << p->schema_location().string() << "\n"; + schema_ptr = p; + } + + ++rit; + } + } + + //assert(schema_ptr != tentative_target_); + + //std::cout << "dynamic_ref_validator.do_validate " << "keywordLocation: << " << this->schema_location().string() << ", instanceLocation:" << instance_location.string() << "\n"; + + evaluation_context this_context(context, this->keyword_name()); + return schema_ptr->validate(this_context, instance, instance_location, results, reporter, patch); + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + auto rit = context.dynamic_scope().rbegin(); + auto rend = context.dynamic_scope().rend(); + + const schema_validator *schema_ptr = tentative_target_; + + JSONCONS_ASSERT(schema_ptr != nullptr); + + if (value_.has_plain_name_fragment() && schema_ptr->dynamic_anchor()) + { + while (rit != rend) + { + auto p = (*rit)->get_schema_for_dynamic_anchor(schema_ptr->dynamic_anchor()->fragment()); + if (p != nullptr) + { + schema_ptr = p; + } + ++rit; + } + } + + evaluation_context this_context(context, this->keyword_name()); + return schema_ptr->walk(this_context, instance, instance_location, reporter); + } + }; + + // contentEncoding + + template + class content_encoding_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::string content_encoding_; + + public: + content_encoding_validator(const Json& schema, const uri& schema_location, const std::string& content_encoding) + : keyword_validator_base("contentEncoding", schema, schema_location), + content_encoding_(content_encoding) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_string()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + if (content_encoding_ == "base64") + { + auto s = instance.template as(); + std::string content; + auto retval = jsoncons::decode_base64(s.begin(), s.end(), content); + if (retval.ec != jsoncons::conv_errc::success) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Content is not a base64 string")); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (!content_encoding_.empty()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "unable to check for contentEncoding '" + content_encoding_ + "'")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // contentMediaType + + template + class content_media_type_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::string content_media_type_; + std::string content_encoding_; + + public: + content_media_type_validator(const Json& schema, const uri& schema_location, const std::string& content_media_type, + const std::string& content_encoding) + : keyword_validator_base("contentMediaType", schema, schema_location), + content_media_type_(content_media_type), content_encoding_(content_encoding) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_string()) + { + return walk_result::advance; + } + + std::string str = instance.as_string(); + if (content_encoding_ == "base64") + { + std::string content; + auto retval = jsoncons::decode_base64(str.begin(), str.end(), content); + if (retval.ec != jsoncons::conv_errc::success) + { + return walk_result::advance; + } + str = std::move(content); + } + + evaluation_context this_context(context, this->keyword_name()); + + if (content_media_type_ == "application/json") + { + json_string_reader reader(str); + std::error_code ec; + reader.read(ec); + + if (ec) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::string("Content is not JSON: ") + ec.message())); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // format + + template + class format_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + format_checker format_check_; + + public: + format_validator(const Json& schema, const uri& schema_location, const format_checker& format_check) + : keyword_validator_base("format", schema, schema_location), format_check_(format_check) + { + + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_string()) + { + return walk_result::advance; + } + + if (format_check_ != nullptr) + { + evaluation_context this_context(context, this->keyword_name()); + auto s = instance.template as(); + + walk_result result = format_check_(this_context.eval_path(), this->schema_location(), instance_location, s, reporter); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // pattern + +#if defined(JSONCONS_HAS_STD_REGEX) + template + class pattern_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::string pattern_string_; + std::regex regex_; + + public: + pattern_validator(const Json& schema, const uri& schema_location, + const std::string& pattern_string, const std::regex& regex) + : keyword_validator_base("pattern", schema, schema_location), + pattern_string_(pattern_string), regex_(regex) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_string()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + auto s = instance.template as(); + if (!std::regex_search(s, regex_)) + { + std::string message("String '"); + message.append(s); + message.append("' does not match pattern '"); + message.append(pattern_string_); + message.append("'."); + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::move(message))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; +#else + template + class pattern_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + public: + pattern_validator(const Json& schema, const uri& schema_location) + : keyword_validator_base("pattern", schema, schema_location) + { + } + + private: + + walk_result do_validate(const Json&, + const jsonpointer::json_pointer&, + evaluation_results& /*results*/, + error_reporter&, + Json& /*patch*/) const final + { + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; +#endif + + // maxLength + + template + class max_length_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t max_length_; + public: + max_length_validator(const Json& schema, const uri& schema_location, std::size_t max_length) + : keyword_validator_base("maxLength", schema, schema_location), max_length_(max_length) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_string()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + auto sv = instance.as_string_view(); + std::size_t length = unicode_traits::count_codepoints(sv.data(), sv.size()); + if (length > max_length_) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::string("Number of characters must be at most ") + std::to_string(max_length_))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // maxItems + + template + class max_items_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t max_items_; + public: + max_items_validator(const Json& schema, const uri& schema_location, std::size_t max_items) + : keyword_validator_base("maxItems", schema, schema_location), max_items_(max_items) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + if (instance.size() > max_items_) + { + std::string message("Maximum number of items is " + std::to_string(max_items_)); + message.append(" but found " + std::to_string(instance.size())); + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::move(message))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // minItems + + template + class min_items_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t min_items_; + public: + min_items_validator(const Json& schema, const uri& schema_location, std::size_t min_items) + : keyword_validator_base("minItems", schema, schema_location), min_items_(min_items) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + if (instance.size() < min_items_) + { + std::string message("Minimum number of items is " + std::to_string(min_items_)); + message.append(" but found " + std::to_string(instance.size())); + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::move(message))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // items + + template + class items_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type schema_val_; + public: + items_validator(const std::string& keyword_name, const Json& schema, const uri& schema_location, + schema_validator_type&& schema_val) + : keyword_validator_base(keyword_name, schema, schema_location), + schema_val_(std::move(schema_val)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + if (instance.size() > 0 && schema_val_) + { + if (schema_val_->always_fails()) + { + jsonpointer::json_pointer item_location = instance_location / 0; + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + item_location, + "Item at index '0' but the schema does not allow any items.")); + return result; + } + else if (schema_val_->always_succeeds()) + { + if (context.require_evaluated_items()) + { + results.evaluated_items.insert(range{0,instance.size()}); + } + } + else + { + size_t index = 0; + size_t start = 0; + size_t end = 0; + for (const auto& item : instance.array_range()) + { + jsonpointer::json_pointer item_location = instance_location / index; + std::size_t errors = reporter.error_count(); + walk_result result = schema_val_->validate(this_context, item, item_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (errors == reporter.error_count()) + { + if (context.require_evaluated_items()) + { + if (end == start) + { + start = end = index; + } + ++end; + } + } + else + { + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + ++index; + } + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + if (schema_val_) + { + size_t index = 0; + for (const auto& item : instance.array_range()) + { + jsonpointer::json_pointer item_location = instance_location / index; + result = schema_val_->walk(context, item, item_location, reporter); + if (result == walk_result::abort) + { + return result; + } + ++index; + } + } + return walk_result::advance; + } + }; + + // items + + // uniqueItems + + template + class unique_items_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + bool are_unique_; + public: + unique_items_validator(const Json& schema, const uri& schema_location, bool are_unique) + : keyword_validator_base("uniqueItems", schema, schema_location), are_unique_(are_unique) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + if (are_unique_ && !array_has_unique_items(instance)) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Array items are not unique")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + static bool array_has_unique_items(const Json& a) + { + for (auto it = a.array_range().begin(); it != a.array_range().end(); ++it) + { + for (auto jt = it+1; jt != a.array_range().end(); ++jt) + { + if (*it == *jt) + { + return false; // contains duplicates + } + } + } + return true; // elements are unique + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // minLength + + template + class min_length_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t min_length_; + + public: + min_length_validator(const Json& schema, const uri& schema_location, std::size_t min_length) + : keyword_validator_base("minLength", schema, schema_location), min_length_(min_length) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_string()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + auto sv = instance.as_string_view(); + std::size_t length = unicode_traits::count_codepoints(sv.data(), sv.size()); + if (length < min_length_) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::string("Number of characters must be at least ") + std::to_string(min_length_))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // not + + template + class not_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type schema_val_; + + public: + not_validator(const Json& schema, const uri& schema_location, + schema_validator_type&& schema_val) + : keyword_validator_base("not", schema, schema_location), + schema_val_(std::move(schema_val)) + { + } + + bool always_fails() const final + { + return schema_val_ ? schema_val_->always_succeeds() : false;; + } + + bool always_succeeds() const final + { + return schema_val_ ? schema_val_->always_fails() : false;; + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + evaluation_context this_context(context, this->keyword_name()); + + evaluation_results local_results; + collecting_error_listener local_reporter; + walk_result result = schema_val_->validate(this_context, instance, instance_location, local_results, local_reporter, patch); + if (result == walk_result::abort) + { + return result; + } + + if (local_reporter.errors.empty()) + { + result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Instance must not be valid against schema")); + if (result == walk_result::abort) + { + return result; + } + } + else + { + results.merge(local_results); + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class any_of_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::vector validators_; + + public: + any_of_validator(const Json& schema, const uri& schema_location, + std::vector&& validators) + : keyword_validator_base("anyOf", schema, schema_location), + validators_(std::move(validators)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + //std::cout << "any_of_validator.do_validate " << context.eval_path().string() << ", " << instance << "\n"; + + collecting_error_listener local_reporter; + + evaluation_context this_context(context, this->keyword_name()); + + evaluation_results local_results1; + std::size_t count = 0; + for (std::size_t i = 0; i < validators_.size(); ++i) + { + evaluation_results local_results2; + evaluation_context item_context(this_context, i); + + std::size_t errors = local_reporter.errors.size(); + walk_result result = validators_[i]->validate(item_context, instance, instance_location, local_results2, local_reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (errors == local_reporter.errors.size()) + { + local_results1.merge(local_results2); + ++count; + } + //std::cout << "success: " << i << " " << success << "\n"; + } + + if (count > 0) + { + results.merge(local_results1); + } + else + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Must be valid against at least one schema, but found no matching schemas", + local_reporter.errors)); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + evaluation_context this_context(context, this->keyword_name()); + + for (std::size_t i = 0; i < validators_.size(); ++i) + { + evaluation_context item_context(this_context, i); + + result = validators_[i]->walk(item_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + + return walk_result::advance; + } + }; + + template + class one_of_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::vector validators_; + + public: + one_of_validator(const Json& schema, const uri& schema_location, + std::vector&& validators) + : keyword_validator_base("oneOf", schema, schema_location), + validators_(std::move(validators)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + //std::cout << "any_of_validator.do_validate " << context.eval_path().string() << ", " << instance << "\n"; + + collecting_error_listener local_reporter; + + evaluation_context this_context(context, this->keyword_name()); + + evaluation_results local_results1; + std::vector indices; + for (std::size_t i = 0; i < validators_.size(); ++i) + { + evaluation_results local_results2; + evaluation_context item_context(this_context, i); + + std::size_t errors = local_reporter.errors.size(); + walk_result result = validators_[i]->validate(item_context, instance, instance_location, local_results2, local_reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (errors == local_reporter.errors.size()) + { + local_results1.merge(local_results2); + indices.push_back(i); + } + //std::cout << "success: " << i << " " << success << "\n"; + } + + + if (indices.size() == 1) + { + results.merge(local_results1); + } + else + { + std::string message; + if (indices.size() == 0) + { + message = "Must be valid against exactly one schema, but found no matching schemas"; + } + else + { + message = "Must be valid against exactly one schema, but found " + std::to_string(indices.size()) + " matching schemas at indices "; + for (std::size_t i = 0; i < indices.size(); ++i) + { + if (i > 0) + { + message.push_back(','); + } + message.append(std::to_string(i)); + } + } + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message, + local_reporter.errors)); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (std::size_t i = 0; i < validators_.size(); ++i) + { + evaluation_context item_context(this_context, i); + + result = validators_[i]->walk(item_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + + return walk_result::advance; + } + }; + + template + class all_of_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::vector validators_; + + public: + all_of_validator(const Json& schema, const uri& schema_location, + std::vector&& validators) + : keyword_validator_base("allOf", schema, schema_location), + validators_(std::move(validators)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + //std::cout << this->keyword_name() << " [" << context.eval_path().string() << ", " << this->schema_location().string() << "]\n"; + + evaluation_results local_results1; + collecting_error_listener local_reporter; + + evaluation_context this_context(context, this->keyword_name()); + + std::size_t count = 0; + for (std::size_t i = 0; i < validators_.size(); ++i) + { + evaluation_results local_results2; + evaluation_context item_context(this_context, i); + + std::size_t errors = local_reporter.errors.size(); + walk_result result = validators_[i]->validate(item_context, instance, instance_location, local_results2, local_reporter, patch); + if (result == walk_result::abort) + { + return result; + } + //std::cout << "local_results2:\n"; + //for (const auto& s : local_results2.evaluated_items) + //{ + // std::cout << " " << s << "\n"; + //} + if (errors == local_reporter.errors.size()) + { + local_results1.merge(local_results2); + ++count; + } + //std::cout << "success: " << i << " " << success << "\n"; + } + + //std::cout << "local_results1:\n"; + //for (const auto& s : local_results1.evaluated_items) + //{ + // std::cout << " " << s << "\n"; + //} + + if (count == validators_.size()) + { + results.merge(local_results1); + } + else + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Must be valid against all schemas, but found unmatched schemas", + local_reporter.errors)); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + evaluation_context this_context(context, this->keyword_name()); + + for (std::size_t i = 0; i < validators_.size(); ++i) + { + evaluation_context item_context(this_context, i); + + result = validators_[i]->walk(item_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + + return walk_result::advance; + } + }; + + template + class maximum_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + Json value_; + std::string message_; + + public: + maximum_validator(const Json& schema, const uri& schema_location, const Json& value) + : keyword_validator_base("maximum", schema, schema_location), value_(value), + message_{"Maximum value is " + value.template as() + " but found"} + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + evaluation_context this_context(context, this->keyword_name()); + + if (instance.is_int64() && value_.is_int64()) + { + if (instance.template as() > value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_uint64() && value_.is_uint64()) + { + if (instance.template as() > value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_string_view() && instance.tag() == semantic_tag::bigint) + { + auto sv1 = instance.as_string_view(); + bigint n1 = bigint::from_string(sv1.data(), sv1.length()); + auto s2 = value_.as_string(); + bigint n2 = bigint::from_string(s2.data(), s2.length()); + if (n1 > n2) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_number()) + { + if (instance.template as() > value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class exclusive_maximum_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + Json value_; + std::string message_; + + public: + exclusive_maximum_validator(const Json& schema, const uri& schema_location, const Json& value) + : keyword_validator_base("exclusiveMaximum", schema, schema_location), value_(value), + message_{"Exclusive maximum value is " + value.template as() + " but found "} + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + evaluation_context this_context(context, this->keyword_name()); + + if (instance.is_int64() && value_.is_int64()) + { + if (instance.template as() >= value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_uint64() && value_.is_uint64()) + { + if (instance.template as() >= value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_string_view() && instance.tag() == semantic_tag::bigint) + { + auto sv1 = instance.as_string_view(); + bigint n1 = bigint::from_string(sv1.data(), sv1.length()); + auto s2 = value_.as_string(); + bigint n2 = bigint::from_string(s2.data(), s2.length()); + if (n1 >= n2) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_number()) + { + if (instance.template as() >= value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class minimum_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + Json value_; + std::string message_; + + public: + minimum_validator(const Json& schema, const uri& schema_location, const Json& value) + : keyword_validator_base("minimum", schema, schema_location), value_(value), + message_{"Minimum value is " + value.template as() + " but found "} + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + evaluation_context this_context(context, this->keyword_name()); + + if (instance.is_int64() && value_.is_int64()) + { + if (instance.template as() < value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_uint64() && value_.is_uint64()) + { + if (instance.template as() < value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_string_view() && instance.tag() == semantic_tag::bigint) + { + auto sv1 = instance.as_string_view(); + bigint n1 = bigint::from_string(sv1.data(), sv1.length()); + auto s2 = value_.as_string(); + bigint n2 = bigint::from_string(s2.data(), s2.length()); + if (n1 < n2) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_number()) + { + if (instance.template as() < value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class exclusive_minimum_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + Json value_; + std::string message_; + + public: + exclusive_minimum_validator(const Json& schema, const uri& schema_location, const Json& value) + : keyword_validator_base("exclusiveMinimum", schema, schema_location), value_(value), + message_{"Exclusive minimum value is " + value.template as() + " but found "} + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + evaluation_context this_context(context, this->keyword_name()); + + if (instance.is_int64() && value_.is_int64()) + { + if (instance.template as() <= value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_uint64() && value_.is_uint64()) + { + if (instance.template as() <= value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_string_view() && instance.tag() == semantic_tag::bigint) + { + auto sv1 = instance.as_string_view(); + bigint n1 = bigint::from_string(sv1.data(), sv1.length()); + auto s2 = value_.as_string(); + bigint n2 = bigint::from_string(s2.data(), s2.length()); + if (n1 <= n2) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (instance.is_number()) + { + if (instance.template as() <= value_.template as()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message_ + instance.template as())); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class multiple_of_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + double value_; + + public: + multiple_of_validator(const Json& schema, const uri& schema_location, double value) + : keyword_validator_base("multipleOf", schema, schema_location), value_(value) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_number()) + { + return walk_result::advance; + } + evaluation_context this_context(context, this->keyword_name()); + + double value = instance.template as(); + if (value != 0) // Exclude zero + { + if (!is_multiple_of(value, static_cast(value_))) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + instance.template as() + " is not a multiple of " + std::to_string(value_))); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + static bool is_multiple_of(double x, double multiple_of) + { + double rem = std::remainder(x, multiple_of); + double eps = std::nextafter(x, 0) - x; + return std::fabs(rem) < std::fabs(eps); + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class required_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::vector items_; + + public: + required_validator(const Json& schema, const uri& schema_location, + const std::vector& items) + : keyword_validator_base("required", schema, schema_location), items_(items) + { + } + + required_validator(const required_validator&) = delete; + required_validator(required_validator&&) = default; + required_validator& operator=(const required_validator&) = delete; + required_validator& operator=(required_validator&&) = default; + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (const auto& key : items_) + { + if(instance.find(key) == instance.object_range().end()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Required property '" + key + "' not found.")); + if(result == walk_result::abort) + { + return result; + } + } + } + + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // maxProperties + + template + class max_properties_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t max_properties_; + public: + max_properties_validator(const Json& schema, const uri& schema_location, std::size_t max_properties) + : keyword_validator_base("maxProperties", schema, schema_location), max_properties_(max_properties) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + if (instance.size() > max_properties_) + { + evaluation_context this_context(context, this->keyword_name()); + + std::string message("Maximum number of properties is " + std::to_string(max_properties_)); + message.append(" but found " + std::to_string(instance.size())); + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::move(message))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // minProperties + + template + class min_properties_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t min_properties_; + public: + min_properties_validator(const Json& schema, const uri& schema_location, std::size_t min_properties) + : keyword_validator_base("minProperties", schema, schema_location), min_properties_(min_properties) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + if (instance.size() < min_properties_) + { + evaluation_context this_context(context, this->keyword_name()); + + std::string message("Minimum number of properties is " + std::to_string(min_properties_)); + message.append(" but found " + std::to_string(instance.size())); + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::move(message))); + if (result == walk_result::abort) + { + return result; + } + } + + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class conditional_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type if_val_; + schema_validator_type then_val_; + schema_validator_type else_val_; + + public: + conditional_validator(const Json& schema, const uri& schema_location, + schema_validator_type&& if_val, + schema_validator_type&& then_val, + schema_validator_type&& else_val + ) : keyword_validator_base("", schema, std::move(schema_location)), + if_val_(std::move(if_val)), + then_val_(std::move(then_val)), + else_val_(std::move(else_val)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (if_val_) + { + collecting_error_listener local_reporter; + evaluation_results local_results; + + evaluation_context if_context(context, "if"); + walk_result result = if_val_->validate(if_context, instance, instance_location, local_results, local_reporter, patch); + if (result == walk_result::abort) + { + return result; + } + //std::cout << "if: evaluated properties\n"; + //for (auto& item : results.evaluated_properties) + //{ + // std::cout << " " << item << "\n"; + //} + if (local_reporter.errors.empty()) + { + results.merge(local_results); + if (then_val_) + { + evaluation_context then_context(context, "then"); + result = then_val_->validate(then_context, instance, instance_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + //std::cout << "then: evaluated properties\n"; + //for (auto& item : results.evaluated_properties) + //{ + // std::cout << " " << item << "\n"; + //} + } + } + else + { + if (else_val_) + { + evaluation_context else_context(context, "else"); + result = else_val_->validate(else_context, instance, instance_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + //std::cout << "else: evaluated properties\n"; + //for (auto& item : results.evaluated_properties) + //{ + // std::cout << " " << item << "\n"; + //} + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + walk_result result = walk_result::advance; + if (if_val_) + { + evaluation_context if_context(context, "if"); + result = if_val_->walk(if_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + + if (then_val_) + { + evaluation_context then_context(context, "then"); + result = then_val_->walk(then_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + if (else_val_) + { + evaluation_context else_context(context, "else"); + result = else_val_->walk(else_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + }; + + // enum_validator + + template + class enum_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + Json value_; + + public: + enum_validator(const Json& schema, const uri& schema_location, const Json& sch) + : keyword_validator_base("enum", schema, schema_location), value_(sch) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + evaluation_context this_context(context, this->keyword_name()); + + bool in_range = false; + for (const auto& item : value_.array_range()) + { + if (item == instance) + { + in_range = true; + break; + } + } + + if (!in_range) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "'" + instance.template as() + "' is not a valid enum value.")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // const_validator + + template + class const_validator : public keyword_validator_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + Json value_; + + public: + const_validator(const Json& schema, const uri& schema_location, const Json& sch) + : keyword_validator_base("const", schema, schema_location), value_(sch) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (value_ != instance) + { + evaluation_context this_context(context, this->keyword_name()); + + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Instance is not const")); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + enum class json_schema_type{null,object,array,string,boolean,integer,number}; + + inline + std::string to_string(json_schema_type type) + { + switch (type) + { + case json_schema_type::null: + return "null"; + case json_schema_type::object: + return "object"; + case json_schema_type::array: + return "array"; + case json_schema_type::string: // OK + return "string"; + case json_schema_type::boolean: // OK + return "boolean"; + case json_schema_type::integer: + return "integer"; + case json_schema_type::number: + return "number"; + default: + return "unknown"; + } + + } + + template + class type_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::vector expected_types_; + + public: + type_validator(const type_validator&) = delete; + type_validator& operator=(const type_validator&) = delete; + type_validator(type_validator&&) = default; + type_validator& operator=(type_validator&&) = default; + + type_validator(const Json& schema, const uri& schema_location, + std::vector&& expected_types) + : keyword_validator_base("type", schema, std::move(schema_location)), + expected_types_(std::move(expected_types)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + //std::cout << "type_validator.do_validate " << context.eval_path().string() << instance << "\n"; + //for (auto& type : expected_types_ ) + //{ + // std::cout << " " << to_string(type) << "\n"; + //} + + evaluation_context this_context(context, this->keyword_name()); + + bool is_type_found = expected_types_.empty(); + + auto end = expected_types_.end(); + for (auto it = expected_types_.begin(); it != end && !is_type_found; ++it) + { + switch (*it) + { + case json_schema_type::null: + if (instance.is_null()) // OK + { + is_type_found = true; + } + break; + case json_schema_type::object: + if (instance.is_object()) // OK + { + is_type_found = true; + } + break; + case json_schema_type::array: + if (instance.is_array()) // OK + { + is_type_found = true; + } + break; + case json_schema_type::string: // OK + if (instance.is_string() && instance.tag() != semantic_tag::bigint) + { + is_type_found = true; + } + break; + case json_schema_type::boolean: // OK + if (instance.is_bool()) + { + is_type_found = true; + } + break; + case json_schema_type::integer: + if (instance.is_int64() || instance.is_uint64()) + { + is_type_found = true; + } + else if (instance.is_double() && static_cast(instance.template as()) == instance.template as()) + { + is_type_found = true; + } + else if (instance.is_string() && instance.tag() == semantic_tag::bigint) + { + is_type_found = true; + } + break; + case json_schema_type::number: + if (instance.is_number()) + { + is_type_found = true; + } + break; + default: + break; + } + } + + if (!is_type_found) + { + std::string message = "Expected "; + for (std::size_t i = 0; i < expected_types_.size(); ++i) + { + if (i > 0) + { + message.append(", "); + if (i+1 == expected_types_.size()) + { + message.append("or "); + } + } + message.append(to_string(expected_types_[i])); + } + message.append(", found "); + message.append(to_schema_type(instance.type())); + + return reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + message)); + } + return walk_result::advance; + } + + std::string to_schema_type(json_type type) const + { + switch (type) + { + case json_type::null_value: + { + return "null"; + } + case json_type::bool_value: + { + return "boolean"; + } + case json_type::int64_value: + case json_type::uint64_value: + { + return "integer"; + } + case json_type::half_value: + case json_type::double_value: + { + return "number"; + } + case json_type::string_value: + { + return "string"; + } + case json_type::array_value: + { + return "array"; + } + case json_type::object_value: + { + return "object"; + } + default: + { + return "unsupported type"; + } + } + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class properties_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::map properties_; + public: + properties_validator(const properties_validator&) = delete; + properties_validator& operator=(const properties_validator&) = delete; + properties_validator(properties_validator&&) = default; + properties_validator& operator=(properties_validator&&) = default; + + properties_validator(const Json& schema, const uri& schema_location, + std::map&& properties + ) + : keyword_validator_base("properties", schema, std::move(schema_location)), + properties_(std::move(properties)) + { + } + + walk_result validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch, + std::unordered_set& allowed_properties) const + { + //std::cout << "properties_validator begin[" << context.eval_path().string() << "," << this->schema_location().string() << "]\n"; + if (!instance.is_object()) + { + return walk_result::advance; + } + + //std::cout << "results:\n"; + //for (const auto& s : results) + //{ + // std::cout << " " << s << "\n"; + //} + //std::cout << "\n"; + + evaluation_context this_context(context, this->keyword_name()); + + for (const auto& prop : instance.object_range()) + { + auto prop_it = properties_.find(prop.key()); + + // check if it is in "properties" + if (prop_it != properties_.end()) + { + evaluation_context prop_context{this_context, prop.key(), evaluation_flags{}}; + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + + std::size_t errors = reporter.error_count(); + walk_result result = prop_it->second->validate(prop_context, prop.value(), prop_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + allowed_properties.insert(prop.key()); + if (errors == reporter.error_count()) + { + if (context.require_evaluated_properties()) + { + results.evaluated_properties.insert(prop.key()); + } + } + } + } + // Any property that doesn't match any of the property names in the properties keyword is ignored by this keyword. + + // reverse search + for (auto const& prop : properties_) + { + //std::cout << " prop:" << prop.first << "\n"; + const auto finding = instance.find(prop.first); + if (finding == instance.object_range().end()) + { + // If prop is not in instance + auto default_value = prop.second->get_default_value(); + if (default_value) + { + // If default value is available, update patch + jsonpointer::json_pointer prop_location = instance_location / prop.first; + + update_patch(patch, prop_location, std::move(*default_value)); + } + } + } + //std::cout << "properties_validator end[" << context.eval_path().string() << "," << this->schema_location().string() << "]"; + //std::cout << "results:\n"; + //for (const auto& s : results) + //{ + // std::cout << " " << s << "\n"; + //} + //std::cout << "\n"; + return walk_result::advance; + } + + walk_result walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter, + std::unordered_set& allowed_properties) const + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + for (const auto& prop : instance.object_range()) + { + auto prop_it = properties_.find(prop.key()); + + if (prop_it != properties_.end()) + { + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + result = prop_it->second->walk(context, prop.value(), prop_location, reporter); + allowed_properties.insert(prop.key()); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + std::unordered_set allowed_properties; + return validate(context, instance, instance_location, results, reporter, patch, allowed_properties); + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + std::unordered_set allowed_properties; + return walk(context, instance, instance_location, reporter, allowed_properties); + } + + void update_patch(Json& patch, const jsonpointer::json_pointer& instance_location, Json&& default_value) const + { + Json j; + j.try_emplace("op", "add"); + j.try_emplace("path", instance_location.string()); + j.try_emplace("value", std::forward(default_value)); + patch.push_back(std::move(j)); + } + }; + + template + class pattern_properties_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::vector> pattern_properties_; + + public: + pattern_properties_validator(const Json& schema, const uri& schema_location, + std::vector>&& pattern_properties + ) + : keyword_validator_base("patternProperties", schema, std::move(schema_location)), + pattern_properties_(std::move(pattern_properties)) + { + } + + walk_result validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch, + std::unordered_set& allowed_properties) const + { + (void)context; + (void)instance; + (void)instance_location; + (void)results; + (void)reporter; + (void)patch; + (void)allowed_properties; +#if defined(JSONCONS_HAS_STD_REGEX) + if (!instance.is_object()) + { + return walk_result::advance; + } + evaluation_context this_context(context, this->keyword_name()); + for (const auto& prop : instance.object_range()) + { + evaluation_context prop_context{this_context, prop.key(), evaluation_flags{}}; + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + + // check all matching "patternProperties" + for (auto& schema_pp : pattern_properties_) + { + if (std::regex_search(prop.key(), schema_pp.first)) + { + allowed_properties.insert(prop.key()); + std::size_t errors = reporter.error_count(); + walk_result result = schema_pp.second->validate(prop_context, prop.value() , prop_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (errors == reporter.error_count()) + { + if (context.require_evaluated_properties()) + { + results.evaluated_properties.insert(prop.key()); + } + } + } + } + } +#endif + return walk_result::advance; + } + + walk_result walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter, + std::unordered_set& allowed_properties) const + { + if (!instance.is_object()) + { + return walk_result::advance; + } + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + (void)context; +#if defined(JSONCONS_HAS_STD_REGEX) + evaluation_context this_context(context, this->keyword_name()); + for (const auto& prop : instance.object_range()) + { + evaluation_context prop_context{this_context, prop.key(), evaluation_flags{}}; + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + + // check all matching "patternProperties" + for (auto& schema_pp : pattern_properties_) + { + if (std::regex_search(prop.key(), schema_pp.first)) + { + allowed_properties.insert(prop.key()); + result = schema_pp.second->walk(prop_context, prop.value() , prop_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + } +#endif + return walk_result::advance; + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + std::unordered_set allowed_properties; + return validate(context, instance, instance_location, results, reporter, patch, allowed_properties); + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + std::unordered_set allowed_properties; + return walk(context,instance, instance_location, reporter, allowed_properties); + } + }; + + template + class additional_properties_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::unique_ptr> properties_; + std::unique_ptr> pattern_properties_; + schema_validator_type additional_properties_; + + public: + additional_properties_validator(const Json& schema, const uri& schema_location, + std::unique_ptr>&& properties, + std::unique_ptr>&& pattern_properties, + schema_validator_type&& additional_properties + ) + : keyword_validator_base("additionalProperties", schema, std::move(schema_location)), + properties_(std::move(properties)), + pattern_properties_(std::move(pattern_properties)), + additional_properties_(std::move(additional_properties)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + std::unordered_set allowed_properties; + + if (properties_) + { + walk_result result = properties_->validate(context, instance, instance_location, results, reporter, patch, allowed_properties); + if (result == walk_result::abort) + { + return result; + } + } + + if (pattern_properties_) + { + walk_result result = pattern_properties_->validate(context, instance, instance_location, results, reporter, patch, allowed_properties); + if (result == walk_result::abort) + { + return result; + } + } + + if (additional_properties_) + { + evaluation_context this_context(context, this->keyword_name()); + if (additional_properties_->always_fails()) + { + for (const auto& prop : instance.object_range()) + { + evaluation_context prop_context{this_context, prop.key(), evaluation_flags{}}; + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + // check if it is in "allowed properties" + auto prop_it = allowed_properties.find(prop.key()); + if (prop_it == allowed_properties.end()) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + prop_context.eval_path(), + additional_properties_->schema_location(), + prop_location, + "Additional property '" + prop.key() + "' not allowed by schema.")); + if (result == walk_result::abort) + { + return result; + } + break; + } + } + } + else if (additional_properties_->always_succeeds()) + { + if (context.require_evaluated_properties()) + { + for (const auto& prop : instance.object_range()) + { + results.evaluated_properties.insert(prop.key()); + } + } + } + else + { + for (const auto& prop : instance.object_range()) + { + // check if it is in "allowed properties" + auto prop_it = allowed_properties.find(prop.key()); + if (prop_it == allowed_properties.end()) + { + evaluation_context prop_context{this_context, prop.key(), evaluation_flags{}}; + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + + // finally, check "additionalProperties" + //std::cout << "additional_properties_validator a_prop_or_pattern_matched " << a_prop_or_pattern_matched << ", " << bool(additional_properties_); + + //std::cout << " !!!additionalProperties!!!"; + collecting_error_listener local_reporter; + + walk_result result = additional_properties_->validate(prop_context, prop.value() , prop_location, results, local_reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (!local_reporter.errors.empty()) + { + result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + additional_properties_->schema_location().string(), + instance_location, + "Additional property '" + prop.key() + "' found but was invalid.")); + if (result == walk_result::abort) + { + return result; + } + } + else if (context.require_evaluated_properties()) + { + results.evaluated_properties.insert(prop.key()); + } + + } + //std::cout << "\n"; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + std::unordered_set allowed_properties; + if (properties_) + { + result = properties_->walk(context, instance, instance_location, reporter, allowed_properties); + if (result == walk_result::abort) + { + return result; + } + } + + if (pattern_properties_) + { + result = pattern_properties_->walk(context, instance, instance_location, reporter, allowed_properties); + if (result == walk_result::abort) + { + return result; + } + } + + if (additional_properties_) + { + evaluation_context this_context(context, this->keyword_name()); + for (const auto& prop : instance.object_range()) + { + // check if it is in "allowed properties" + auto prop_it = allowed_properties.find(prop.key()); + if (prop_it == allowed_properties.end()) + { + evaluation_context prop_context{this_context, prop.key(), evaluation_flags{}}; + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + + result = additional_properties_->walk(prop_context, prop.value() , prop_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + } + return walk_result::advance; + } + }; + + template + class dependent_required_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::map dependent_required_; + + public: + dependent_required_validator(const Json& schema, const uri& schema_location, + std::map&& dependent_required + ) + : keyword_validator_base("dependentRequired", schema, std::move(schema_location)), + dependent_required_(std::move(dependent_required)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (const auto& dep : dependent_required_) + { + auto prop = instance.find(dep.first); + if (prop != instance.object_range().end()) + { + // if dependency-prop is present in instance + jsonpointer::json_pointer prop_location = instance_location / dep.first; + walk_result result = dep.second->validate(this_context, instance, prop_location, results, reporter, patch); // validate + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (const auto& dep : dependent_required_) + { + auto prop = instance.find(dep.first); + if (prop != instance.object_range().end()) + { + // if dependency-prop is present in instance + result = dep.second->walk(this_context, instance, instance_location / dep.first, reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + }; + + template + class dependent_schemas_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::map dependent_schemas_; + + public: + dependent_schemas_validator(const Json& schema, const uri& schema_location, + std::map&& dependent_schemas + ) + : keyword_validator_base("dependentSchemas", schema, std::move(schema_location)), + dependent_schemas_(std::move(dependent_schemas)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (const auto& dep : dependent_schemas_) + { + auto prop = instance.find(dep.first); + if (prop != instance.object_range().end()) + { + // if dependency-prop is present in instance + jsonpointer::json_pointer prop_location = instance_location / dep.first; + walk_result result = dep.second->validate(this_context, instance, prop_location, results, reporter, patch); // validate + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (const auto& dep : dependent_schemas_) + { + auto prop = instance.find(dep.first); + if (prop != instance.object_range().end()) + { + // if dependency-prop is present in instance + result = dep.second->walk(this_context, instance, instance_location / dep.first, reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + }; + + template + class property_names_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type schema_val_; + + public: + property_names_validator(const Json& schema, const uri& schema_location, + schema_validator_type&& schema_val + ) + : keyword_validator_base("propertyNames", schema, schema_location), + schema_val_{ std::move(schema_val) } + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + if (instance.size() > 0 && schema_val_) + { + if (schema_val_->always_fails()) + { + jsonpointer::json_pointer item_location = instance_location / 0; + return reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + item_location, + "Instance has properties but the schema does not allow any property names.")); + } + else if (schema_val_->always_succeeds()) + { + return walk_result::advance; + } + else + { + for (const auto& prop : instance.object_range()) + { + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + + walk_result result = schema_val_->validate(this_context, prop.key() , instance_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + evaluation_context this_context(context, this->keyword_name()); + + if (instance.size() > 0 && schema_val_) + { + for (const auto& prop : instance.object_range()) + { + result = schema_val_->walk(this_context, prop.key(), instance_location / prop.key(), reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + }; + + template + class dependencies_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::map dependent_required_; + std::map dependent_schemas_; + + public: + dependencies_validator(const Json& schema, const uri& schema_location, + std::map&& dependent_required, + std::map&& dependent_schemas + ) + : keyword_validator_base("dependencies", schema, std::move(schema_location)), + dependent_required_(std::move(dependent_required)), + dependent_schemas_(std::move(dependent_schemas)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_object()) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (const auto& dep : dependent_required_) + { + auto prop = instance.find(dep.first); + if (prop != instance.object_range().end()) + { + // if dependency-prop is present in instance + jsonpointer::json_pointer prop_location = instance_location / dep.first; + walk_result result = dep.second->validate(this_context, instance, prop_location, results, reporter, patch); // validate + if (result == walk_result::abort) + { + return result; + } + } + } + + for (const auto& dep : dependent_schemas_) + { + auto prop = instance.find(dep.first); + if (prop != instance.object_range().end()) + { + // if dependency-prop is present in instance + jsonpointer::json_pointer prop_location = instance_location / dep.first; + walk_result result = dep.second->validate(this_context, instance, prop_location, results, reporter, patch); // validate + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class max_contains_keyword : public keyword_base + { + using keyword_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t max_value_; + public: + max_contains_keyword(const Json& schema, const uri& schema_location, std::size_t max_value) + : keyword_base("maxContains", schema, schema_location), max_value_(max_value) + { + } + + walk_result validate(const evaluation_context& context, + const jsonpointer::json_pointer& instance_location, + std::size_t count, + error_reporter& reporter) const + { + evaluation_context this_context(context, this->keyword_name()); + + if (count > max_value_) + { + std::string message("A schema can match a contains constraint at most " + std::to_string(max_value_) + " times"); + message.append(" but it matched " + std::to_string(count) + " times."); + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::move(message))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + // minItems + + template + class min_contains_keyword : public keyword_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::size_t min_value_; + public: + min_contains_keyword(const Json& schema, const uri& schema_location, std::size_t min_value) + : keyword_base("minContains", schema, schema_location), min_value_(min_value) + { + } + + walk_result validate(const evaluation_context& context, + const jsonpointer::json_pointer& instance_location, + std::size_t count, + error_reporter& reporter) const + { + evaluation_context this_context(context, this->keyword_name()); + + if (count < min_value_) + { + std::string message("A schema must match a contains constraint at least " + std::to_string(min_value_) + " times"); + message.append(" but it matched " + std::to_string(count) + " times."); + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + std::move(message))); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class contains_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type schema_validator_; + std::unique_ptr> max_contains_; + std::unique_ptr> min_contains_; + + public: + contains_validator(const Json& schema, const uri& schema_location, + schema_validator_type&& schema_validator) + : keyword_validator_base("contains", std::addressof(schema), std::move(schema_location)), + schema_validator_(std::move(schema_validator)) + { + } + + contains_validator(const Json& schema, const uri& schema_location, + schema_validator_type&& schema_validator, + std::unique_ptr>&& max_contains, + std::unique_ptr>&& min_contains) + : keyword_validator_base("contains", schema, std::move(schema_location)), + schema_validator_(std::move(schema_validator)), + max_contains_(std::move(max_contains)), + min_contains_(std::move(min_contains)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + if (!schema_validator_) + { + return walk_result::advance; + } + + evaluation_context this_context(context, this->keyword_name()); + + std::size_t contains_count = 0; + collecting_error_listener local_reporter; + + std::size_t index = 0; + size_t start = 0; + size_t end = 0; + for (const auto& item : instance.array_range()) + { + std::size_t errors = local_reporter.errors.size(); + walk_result result = schema_validator_->validate(this_context, item, instance_location / index, results, local_reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (errors == local_reporter.errors.size()) + { + if (context.require_evaluated_items()) + { + if (end == start) + { + start = end = index; + } + ++end; + } + ++contains_count; + } + else + { + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + ++index; + } + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + + if (max_contains_ || min_contains_) + { + if (max_contains_) + { + walk_result result = max_contains_->validate(this_context, instance_location, contains_count, reporter); + if (result == walk_result::abort) + { + return result; + } + } + if (min_contains_) + { + walk_result result = min_contains_->validate(this_context, instance_location, contains_count, reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + else if (contains_count == 0) + { + walk_result result = reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Expected at least one array item to match 'contains' schema.", + local_reporter.errors)); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const override + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + if (!schema_validator_) + { + return walk_result::advance; + } + + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + evaluation_context this_context(context, this->keyword_name()); + + for (std::size_t index = 0; index < instance.size(); ++index) + { + result = schema_validator_->walk(this_context, instance.at(index), instance_location / index, reporter); + if (result == walk_result::abort) + { + return result; + } + } + + if (max_contains_) + { + result = max_contains_->walk(this_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + if (min_contains_) + { + result = min_contains_->walk(this_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + }; + + template + class items_keyword : public keyword_base + { + using keyword_validator_type = std::unique_ptr>; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type items_val_; + public: + items_keyword(const std::string& keyword_name, const Json& schema, const uri& schema_location, schema_validator_type&& items_val) + : keyword_base(keyword_name, schema, schema_location), items_val_(std::move(items_val)) + { + } + + walk_result validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch, + std::size_t data_index) const + { + if (!instance.is_array()) + { + return walk_result::advance; + } + if (data_index < instance.size() && items_val_) + { + evaluation_context items_context(context, this->keyword_name()); + if (items_val_->always_fails()) + { + jsonpointer::json_pointer item_location = instance_location / data_index; + walk_result result = reporter.error(validation_message(this->keyword_name(), + items_context.eval_path(), + this->schema_location(), + item_location, + "Extra item at index '" + std::to_string(data_index) + "' but the schema does not allow extra items.")); + if (result == walk_result::abort) + { + return result; + } + } + else if (items_val_->always_succeeds()) + { + results.evaluated_items.insert(range{0,instance.size()}); + } + else + { + std::size_t start = 0; + std::size_t end = 0; + for (; data_index < instance.size(); ++data_index) + { + jsonpointer::json_pointer item_location = instance_location / data_index; + std::size_t errors = reporter.error_count(); + walk_result result = items_val_->validate(items_context, instance[data_index], item_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (errors == reporter.error_count()) + { + if (context.require_evaluated_items()) + { + if (end == start) + { + start = end = data_index; + } + ++end; + } + } + else + { + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + } + + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + } + return walk_result::advance; + } + + walk_result walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter, + std::size_t data_index) const + { + if (!instance.is_array()) + { + return walk_result::advance; + } + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + if (data_index < instance.size() && items_val_) + { + evaluation_context items_context(context, this->keyword_name()); + for (; data_index < instance.size(); ++data_index) + { + jsonpointer::json_pointer item_location = instance_location / data_index; + result = items_val_->walk(items_context, instance[data_index], item_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + } + return walk_result::advance; + } + }; + + template + class prefix_items_validator : public keyword_validator_base + { + using schema_validator_type = typename schema_validator::schema_validator_type; + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::vector prefix_item_validators_; + std::unique_ptr> items_val_; + public: + prefix_items_validator(const std::string& keyword_name, const Json& schema, const uri& schema_location, + std::vector&& prefix_item_validators, + std::unique_ptr>&& items_val) + : keyword_validator_base(keyword_name, schema, schema_location), + prefix_item_validators_(std::move(prefix_item_validators)), + items_val_(std::move(items_val)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + size_t data_index = 0; + + evaluation_context prefix_items_context(context, this->keyword_name()); + + size_t start = 0; + size_t end = 0; + for (std::size_t schema_index=0; + schema_index < prefix_item_validators_.size() && data_index < instance.size(); + ++schema_index, ++data_index) + { + auto& val = prefix_item_validators_[schema_index]; + evaluation_context item_context{prefix_items_context, schema_index, evaluation_flags{}}; + jsonpointer::json_pointer item_location = instance_location / data_index; + std::size_t errors = reporter.error_count(); + walk_result result = val->validate(item_context, instance[data_index], item_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (errors == reporter.error_count()) + { + if (context.require_evaluated_items()) + { + if (end == start) + { + start = end = data_index; + } + ++end; + } + } + else + { + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + + } + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + } + + if (data_index < instance.size() && items_val_) + { + walk_result result = items_val_->validate(context, instance, instance_location, results, reporter, patch, data_index); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + if (!instance.is_array()) + { + return walk_result::advance; + } + + walk_result result = reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + if (result == walk_result::abort) + { + return result; + } + + size_t data_index = 0; + + evaluation_context prefix_items_context(context, this->keyword_name()); + + for (std::size_t schema_index=0; + schema_index < prefix_item_validators_.size() && data_index < instance.size(); + ++schema_index, ++data_index) + { + auto& val = prefix_item_validators_[schema_index]; + evaluation_context item_context{prefix_items_context, schema_index, evaluation_flags{}}; + jsonpointer::json_pointer item_location = instance_location / data_index; + result = val->walk(item_context, instance[data_index], item_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + + if (data_index < instance.size() && items_val_) + { + items_val_->walk(context, instance, instance_location, reporter, data_index); + } + return walk_result::advance; + } + }; + + template + class unevaluated_properties_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type schema_val_; + + public: + unevaluated_properties_validator(const Json& schema, const uri& schema_location, + schema_validator_type&& schema_val) + : keyword_validator_base("unevaluatedProperties", schema, std::move(schema_location)), + schema_val_(std::move(schema_val)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + //std::cout << "unevaluated_properties_validator [" << context.eval_path().string() << "," << this->schema_location().string() << "]"; + //std::cout << "results:\n"; + //for (const auto& s : results.evaluated_properties_) + //{ + // std::cout << " " << s << "\n"; + //} + //std::cout << "\n"; + if (!instance.is_object()) + { + return walk_result::advance; + } + + if (schema_val_) + { + evaluation_context this_context(context, this->keyword_name()); + if (schema_val_->always_fails()) + { + for (const auto& prop : instance.object_range()) + { + // check if it is in "evaluated_properties" + auto prop_it = results.evaluated_properties.find(prop.key()); + if (prop_it == results.evaluated_properties.end()) + { + evaluation_context prop_context{this_context, prop.key(), evaluation_flags{}}; + jsonpointer::json_pointer prop_location = instance_location / prop.key(); + + walk_result result = reporter.error(validation_message(this->keyword_name(), + prop_context.eval_path(), + this->schema_location(), + prop_location, + "Unevaluated property '" + prop.key() + "' but the schema does not allow unevaluated properties.")); + if (result == walk_result::abort) + { + return result; + } + break; + } + } + } + else if (schema_val_->always_succeeds()) + { + if (context.require_evaluated_properties()) + { + for (const auto& prop : instance.object_range()) + { + results.evaluated_properties.insert(prop.key()); + } + } + } + else + { + for (const auto& prop : instance.object_range()) + { + // check if it is in "evaluated_properties" + auto prop_it = results.evaluated_properties.find(prop.key()); + if (prop_it == results.evaluated_properties.end()) + { + //std::cout << "Not in evaluated properties: " << prop.key() << "\n"; + std::size_t error_count = reporter.error_count(); + walk_result result = schema_val_->validate(this_context, prop.value() , instance_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (reporter.error_count() == error_count) + { + if (context.require_evaluated_properties()) + { + results.evaluated_properties.insert(prop.key()); + } + } + } + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class unevaluated_items_validator : public keyword_validator_base + { + using keyword_validator_type = typename keyword_validator::keyword_validator_type; + using schema_validator_type = typename schema_validator::schema_validator_type; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + schema_validator_type schema_val_; + + public: + unevaluated_items_validator(const Json& schema, const uri& schema_location, + schema_validator_type&& schema_val + ) + : keyword_validator_base("unevaluatedProperties", schema, std::move(schema_location)), + schema_val_(std::move(schema_val)) + { + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + //std::cout << this->keyword_name() << " [" << context.eval_path().string() << ", " << this->schema_location().string() << "]"; + //std::cout << "results:\n"; + //for (const auto& s : results.evaluated_items) + //{ + // std::cout << " " << s << "\n"; + //} + //std::cout << "\n"; + if (!instance.is_array()) + { + return walk_result::advance; + } + + if (schema_val_) + { + evaluation_context this_context(context, this->keyword_name()); + if (schema_val_->always_fails()) + { + for (std::size_t index = 0; index < instance.size(); ++index) + { + // check if it is in "evaluated_items" + if (!results.evaluated_items.contains(index)) + { + evaluation_context item_context{this_context, index, evaluation_flags{}}; + jsonpointer::json_pointer item_location = instance_location / index; + //std::cout << "Not in evaluated properties: " << item.key() << "\n"; + walk_result result = reporter.error(validation_message(this->keyword_name(), + item_context.eval_path(), + this->schema_location(), + item_location, + "Unevaluated item at index '" + std::to_string(index) + "' but the schema does not allow unevaluated items.")); + if (result == walk_result::abort) + { + return result; + } + break; + } + } + } + else if (schema_val_->always_succeeds()) + { + if (context.require_evaluated_items()) + { + results.evaluated_items.insert(range{0,instance.size()}); + } + } + else + { + std::size_t index = 0; + size_t start = 0; + size_t end = 0; + for (const auto& item : instance.array_range()) + { + // check if it is in "evaluated_items" + if (!results.evaluated_items.contains(index)) + { + evaluation_context item_context{this_context, index, evaluation_flags{}}; + jsonpointer::json_pointer item_location = instance_location / index; + //std::cout << "Not in evaluated properties: " << item.key() << "\n"; + std::size_t error_count = reporter.error_count(); + walk_result result = schema_val_->validate(item_context, item, item_location, results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + if (reporter.error_count() == error_count) + { + if (context.require_evaluated_items()) + { + if (end == start) + { + start = end = index; + } + ++end; + } + } + else + { + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + } + ++index; + } + if (start < end) + { + results.evaluated_items.insert(range{start, end}); + start = end; + } + } + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_COMMON_KEYWORD_VALIDATORS_HPP diff --git a/third_party/jsoncons_ext/jsonschema/common/schema_builder.hpp b/third_party/jsoncons_ext/jsonschema/common/schema_builder.hpp new file mode 100644 index 0000000000..ab4fc3ea88 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/schema_builder.hpp @@ -0,0 +1,1011 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_SCHEMA_BUILDER_HPP +#define JSONCONS_JSONSCHEMA_COMMON_SCHEMA_BUILDER_HPP + +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + template + using schema_resolver = std::function; + + template + class schema_builder + { + public: + using schema_store_type = std::map*>; + using schema_builder_factory_type = std::function>(const Json&, + const evaluation_options&,schema_store_type*,const std::vector>&, + const std::unordered_map&)>; + using keyword_validator_type = typename std::unique_ptr>; + using schema_validator_type = typename std::unique_ptr>; + using ref_validator_type = ref_validator; + using ref_type = ref; + using anchor_uri_map_type = std::unordered_map; + + private: + std::string spec_version_; + std::unique_ptr root_schema_; + schema_builder_factory_type builder_factory_; + evaluation_options options_; + schema_store_type* schema_store_ptr_; + std::vector> resolvers_; + std::unordered_map vocabulary_; + + schema_validator_type root_; + + // Owns external schemas + std::vector schema_validators_; + public: + std::vector> unresolved_refs_; + std::map unknown_keywords_; + + public: + + schema_builder(const std::string& version, Json&& root_schema, const schema_builder_factory_type& builder_factory, + evaluation_options options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers) + : spec_version_(version), builder_factory_(builder_factory), options_(std::move(options)), + schema_store_ptr_(schema_store_ptr), resolvers_(resolvers) + { + JSONCONS_ASSERT(schema_store_ptr != nullptr); + root_schema_ = jsoncons::make_unique(std::move(root_schema)); + } + + schema_builder(const std::string& version, Json&& root_schema, const schema_builder_factory_type& builder_factory, + evaluation_options options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers, + const std::unordered_map& vocabulary) + : spec_version_(version), builder_factory_(builder_factory), options_(std::move(options)), + schema_store_ptr_(schema_store_ptr), resolvers_(resolvers), vocabulary_(vocabulary) + { + JSONCONS_ASSERT(schema_store_ptr != nullptr); + root_schema_ = jsoncons::make_unique(std::move(root_schema)); + } + + virtual ~schema_builder() = default; + + const std::unordered_map& vocabulary() const {return vocabulary_;} + + void save_schema(schema_validator_type&& schema) + { + schema_validators_.emplace_back(std::move(schema)); + } + + const std::string& schema() const + { + return spec_version_; + } + + void build_schema() + { + anchor_uri_map_type anchor_dict; + + root_ = make_schema_validator(compilation_context{}, *root_schema_, {}, anchor_dict); + } + + void build_schema(const std::string& retrieval_uri) + { + anchor_uri_map_type anchor_dict; + root_ = make_schema_validator(compilation_context(uri_wrapper(retrieval_uri)), *root_schema_, {}, anchor_dict); + } + + evaluation_options options() const + { + return options_; + } + + schema_validator_type make_boolean_schema(const compilation_context& context, const Json& sch) + { + uri schema_location = context.get_base_uri(); + schema_validator_type schema_validator_ptr = jsoncons::make_unique>( + schema_location, sch.template as()); + + return schema_validator_ptr; + } + + std::unique_ptr> get_schema_validator() + { + //std::cout << "schema_store:\n"; + //for (auto& member : *schema_store_ptr_) + //{ + // std::cout << " " << member.first.string() << "\n"; + //} + + // load all external schemas that have not already been loaded + // new unresolved refs may be added to the end as earlier ones are resolved + for (std::size_t i = 0; i < unresolved_refs_.size(); ++i) + { + auto loc = unresolved_refs_[i].first; + //std::cout << "unresolved: " << loc.string() << "\n"; + if (schema_store_ptr_->find(loc) == schema_store_ptr_->end()) // registry for this file is empty + { + bool found = false; + for (auto it = resolvers_.begin(); it != resolvers_.end() && !found; ++it) + { + Json external_sch = (*it)(loc.base()); + + if (external_sch.is_object() || external_sch.is_bool()) + { + anchor_uri_map_type anchor_dict2; + this->save_schema(make_cross_draft_schema_validator(compilation_context(uri_wrapper(loc.base())), + std::move(external_sch), {}, anchor_dict2)); + found = true; + } + } + if (found) + { + // Try resolving again + if (schema_store_ptr_->find(loc) == schema_store_ptr_->end()) + { + JSONCONS_THROW(jsonschema::schema_error("Unresolved reference '" + loc.string() + "'")); + } + } + else + { + JSONCONS_THROW(jsonschema::schema_error("Don't know how to load JSON Schema '" + loc.base().string() + "'" )); + } + } + } + + resolve_references(); + + return jsoncons::make_unique>(std::move(root_schema_), std::move(root_), std::move(schema_validators_)); + } + + void resolve_references() + { + for (auto& ref : unresolved_refs_) + { + auto it = schema_store_ptr_->find(ref.first); + if (it == schema_store_ptr_->end()) + { + JSONCONS_THROW(schema_error("Undefined reference " + ref.first.string())); + } + if (it->second == nullptr) + { + JSONCONS_THROW(schema_error("Null referred schema " + ref.first.string())); + } + ref.second->set_referred_schema(it->second); + } + } + + void insert_schema(const uri_wrapper& identifier, schema_validator* s) + { + this->schema_store_ptr_->emplace(identifier.uri(), s); + } + + void insert_unknown_keyword(const uri_wrapper& uri, + const std::string& key, + const Json& value) + { + auto new_u = uri.append(key); + uri_wrapper new_uri(new_u); + + if (new_uri.has_fragment() && !new_uri.has_plain_name_fragment()) + { + // is there a reference looking for this unknown-keyword, which is thus no longer a unknown keyword but a schema + auto unresolved_refs = std::find_if(this->unresolved_refs_.begin(), this->unresolved_refs_.end(), + [new_uri](const std::pair*>& pr) {return pr.first == new_uri.uri();}); + if (unresolved_refs != this->unresolved_refs_.end()) + { + anchor_uri_map_type anchor_dict2; + this->save_schema(make_cross_draft_schema_validator(compilation_context(new_uri), value, {}, anchor_dict2)); + } + else // no, nothing ref'd it, keep for later + { + //file.unknown_keywords.emplace(fragment, value); + this->unknown_keywords_.emplace(new_uri.uri(), value); + } + + // recursively add possible subschemas of unknown keywords + if (value.type() == json_type::object_value) + { + for (const auto& subsch : value.object_range()) + { + insert_unknown_keyword(new_uri, subsch.key(), subsch.value()); + } + } + } + } + + std::unique_ptr> get_or_create_reference(const Json& schema, const uri_wrapper& identifier) + { + // a schema already exists + auto it = this->schema_store_ptr_->find(identifier.uri()); + if (it != this->schema_store_ptr_->end()) + { + return jsoncons::make_unique(schema, identifier.uri(), it->second); + } + + // referencing an unknown keyword, turn it into schema + // + // an unknown keyword can only be referenced by a JSONPointer, + // not by a plain name identifier + if (identifier.has_fragment() && !identifier.has_plain_name_fragment()) + { + //std::string fragment = std::string(identifier.fragment()); + + auto it2 = this->unknown_keywords_.find(identifier.uri()); + if (it2 != this->unknown_keywords_.end()) + { + auto& subsch = it2->second; + anchor_uri_map_type anchor_dict2; + auto s = make_cross_draft_schema_validator(compilation_context(identifier), subsch, {}, anchor_dict2); + this->unknown_keywords_.erase(it2); + auto orig = jsoncons::make_unique(schema, identifier.uri(), s.get()); + this->save_schema(std::move(s)); + return orig; + } + } + + // get or create a ref_validator + auto orig = jsoncons::make_unique(schema, identifier.uri()); + + this->unresolved_refs_.emplace_back(identifier.uri(), orig.get()); + return orig; + } + + static bool validate_anchor(const std::string& s) + { + if (s.empty()) + { + return false; + } + if (!((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z'))) + { + return false; + } + + for (std::size_t i = 1; i < s.size(); ++i) + { + switch (s[i]) + { + case '-': + case '_': + case ':': + case '.': + break; + default: + if (!((s[i] >= 'a' && s[i] <= 'z') || (s[i] >= 'A' && s[i] <= 'Z') || (s[i] >= '0' && s[i] <= '9'))) + { + return false; + } + break; + } + } + return true; + } + + virtual compilation_context make_compilation_context(const compilation_context& parent, + const Json& sch, jsoncons::span keys) const = 0; + + virtual schema_validator_type make_schema_validator(const compilation_context& context, + const Json& sch, jsoncons::span keys, anchor_uri_map_type& anchor_dict) = 0; + + schema_validator_type make_cross_draft_schema_validator(const compilation_context& context, + const Json& sch, jsoncons::span keys, anchor_uri_map_type& anchor_dict) + { + schema_validator_type schema_val = schema_validator_type{}; + switch (std::move(sch).type()) + { + case json_type::object_value: + { + auto it = std::move(sch).find("$schema"); + if (it != std::move(sch).object_range().end()) + { + if (it->value().as_string_view() == schema()) + { + return make_schema_validator(context, std::move(sch), keys, anchor_dict); + } + else + { + auto schema_builder = builder_factory_(std::move(sch), options_, schema_store_ptr_, resolvers_, vocabulary_); + schema_builder->build_schema(context.get_base_uri().string()); + schema_val = schema_builder->get_schema_validator(); + } + } + else + { + return make_schema_validator(context, std::move(sch), keys, anchor_dict); + } + break; + } + case json_type::bool_value: + { + return make_schema_validator(context, std::move(sch), keys, anchor_dict); + } + default: + JSONCONS_THROW(schema_error("Schema must be object or boolean")); + } + return schema_val; + } + + std::unique_ptr> make_properties_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::map properties; + + for (const auto& prop : sch.object_range()) + { + std::string sub_keys[] = + {"properties", prop.key()}; + properties.emplace(std::make_pair(prop.key(), + this->make_cross_draft_schema_validator(context, prop.value(), sub_keys, anchor_dict))); + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(properties)); + } + + virtual std::unique_ptr> make_max_length_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("maxLength"); + if (!sch.is_number()) + { + std::string message("maxLength must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + virtual std::unique_ptr> make_min_length_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("minLength"); + if (!sch.is_number()) + { + std::string message("minLength must be an integer value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + virtual std::unique_ptr> make_not_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.make_schema_location("not"); + std::string not_key[] = { "not" }; + return jsoncons::make_unique>(parent, schema_location, + make_cross_draft_schema_validator(context, sch, not_key, anchor_dict)); + } + + virtual std::unique_ptr> make_const_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("const"); + return jsoncons::make_unique>(parent, schema_location, sch); + } + + virtual std::unique_ptr> make_enum_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("enum"); + return jsoncons::make_unique>(parent, schema_location, sch); + } + + virtual std::unique_ptr> make_required_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("required"); + return jsoncons::make_unique>(parent, schema_location, sch.template as>()); + } + + virtual std::unique_ptr> make_maximum_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("maximum"); + if (!sch.is_number()) + { + std::string message("maximum must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + return jsoncons::make_unique>(parent, schema_location, sch); + } + + virtual std::unique_ptr> make_exclusive_maximum_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("exclusiveMaximum"); + if (!sch.is_number()) + { + std::string message("exclusiveMaximum must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + return jsoncons::make_unique>(parent, schema_location, sch); + } + + virtual std::unique_ptr> make_minimum_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("minimum"); + + if (!sch.is_number()) + { + std::string message("minimum must be an integer"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + return jsoncons::make_unique>(parent, schema_location, sch); + } + + virtual std::unique_ptr> make_exclusive_minimum_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("exclusiveMinimum"); + if (!sch.is_number()) + { + std::string message("exclusiveMinimum must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + return jsoncons::make_unique>(parent, schema_location, sch); + } + + virtual std::unique_ptr> make_multiple_of_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("multipleOf"); + if (!sch.is_number()) + { + std::string message("multipleOf must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + + virtual std::unique_ptr> make_type_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + std::string schema_location = context.get_base_uri().string(); + std::vector expected_types; + + switch (sch.type()) + { + case json_type::string_value: + { + auto type = sch.template as(); + if (type == "null") + { + expected_types.push_back(json_schema_type::null); + } + else if (type == "object") + { + expected_types.push_back(json_schema_type::object); + } + else if (type == "array") + { + expected_types.push_back(json_schema_type::array); + } + else if (type == "string") + { + expected_types.push_back(json_schema_type::string); + } + else if (type == "boolean") + { + expected_types.push_back(json_schema_type::boolean); + } + else if (type == "integer") + { + expected_types.push_back(json_schema_type::integer); + } + else if (type == "number") + { + expected_types.push_back(json_schema_type::number); + } + break; + } + + case json_type::array_value: // "type": ["type1", "type2"] + { + for (const auto& item : sch.array_range()) + { + auto type = item.template as(); + if (type == "null") + { + expected_types.push_back(json_schema_type::null); + } + else if (type == "object") + { + expected_types.push_back(json_schema_type::object); + } + else if (type == "array") + { + expected_types.push_back(json_schema_type::array); + } + else if (type == "string") + { + expected_types.push_back(json_schema_type::string); + } + else if (type == "boolean") + { + expected_types.push_back(json_schema_type::boolean); + } + else if (type == "integer") + { + expected_types.push_back(json_schema_type::integer); + } + else if (type == "number") + { + expected_types.push_back(json_schema_type::number); + } + } + break; + } + default: + break; + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(expected_types) + ); + } + + virtual std::unique_ptr> make_content_encoding_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("contentEncoding"); + if (!sch.is_string()) + { + std::string message("contentEncoding must be a string"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + virtual std::unique_ptr> make_content_media_type_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("contentMediaType"); + if (!sch.is_string()) + { + std::string message("contentMediaType must be a string"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + + std::string content_encoding; + auto it = parent.find("contentEncoding"); + if (it != parent.object_range().end()) + { + if (!it->value().is_string()) + { + std::string message("contentEncoding must be a string"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + + content_encoding = it->value().as_string(); + } + + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value, content_encoding); + } + + virtual std::unique_ptr> make_format_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + auto schema_location = context.make_schema_location("format"); + + std::string format = sch.template as(); + + format_checker format_check; + if (format == "date-time") + { + format_check = rfc3339_date_time_check; + } + else if (format == "date") + { + format_check = rfc3339_date_check; + } + else if (format == "time") + { + format_check = rfc3339_time_check; + } + else if (format == "email") + { + format_check = email_check; + } + else if (format == "hostname") + { + format_check = hostname_check; + } + else if (format == "ipv4") + { + format_check = ipv4_check; + } + else if (format == "ipv6") + { + format_check = ipv6_check; + } + else if (format == "regex") + { + format_check = regex_check; + } + else if (format == "json-pointer") + { + format_check = jsonpointer_check; + } + //else if (format == "uri") + //{ + // format_check = uri_check; + //} + else + { + // Not supported - ignore + format_check = nullptr; + } + + return jsoncons::make_unique>(parent, schema_location, + format_check); + } + + virtual std::unique_ptr> make_pattern_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("pattern"); + auto pattern_string = sch.template as(); + auto regex = std::regex(pattern_string, std::regex::ECMAScript); + return jsoncons::make_unique>(parent, schema_location, + pattern_string, regex); + } + + virtual std::unique_ptr> make_max_items_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("maxItems"); + if (!sch.is_number()) + { + std::string message("maxItems must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + virtual std::unique_ptr> make_min_items_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("minItems"); + if (!sch.is_number()) + { + std::string message("minItems must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + virtual std::unique_ptr> make_max_properties_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("maxProperties"); + if (!sch.is_number()) + { + std::string message("maxProperties must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + virtual std::unique_ptr> make_min_properties_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("minProperties"); + if (!sch.is_number()) + { + std::string message("minProperties must be a number value"); + JSONCONS_THROW(schema_error(schema_location.string() + ": " + message)); + } + auto value = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, value); + } + + virtual std::unique_ptr> make_contains_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.make_schema_location("contains"); + + std::string sub_keys[] = { "contains" }; + + std::unique_ptr> max_contains; + auto it = parent.find("maxContains"); + if (it != parent.object_range().end()) + { + uri path = context.make_schema_location("maxContains"); + auto value = it->value().template as(); + max_contains = jsoncons::make_unique>(parent, path, value); + } + else + { + uri path = context.make_schema_location("maxContains"); + max_contains = jsoncons::make_unique>(parent, path, (std::numeric_limits::max)()); + } + + std::unique_ptr> min_contains; + it = parent.find("minContains"); + if (it != parent.object_range().end()) + { + uri path = context.make_schema_location("minContains"); + auto value = it->value().template as(); + min_contains = jsoncons::make_unique>(parent, path, value); + } + else + { + uri path = context.make_schema_location("minContains"); + min_contains = jsoncons::make_unique>(parent, path, 1); + } + + return jsoncons::make_unique>(parent, schema_location, + make_cross_draft_schema_validator(context, sch, sub_keys, anchor_dict), std::move(max_contains), std::move(min_contains)); + } + + virtual std::unique_ptr> make_unique_items_validator(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("uniqueItems"); + bool are_unique = sch.template as(); + return jsoncons::make_unique>(parent, schema_location, are_unique); + } + + virtual std::unique_ptr> make_all_of_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.make_schema_location("allOf"); + std::vector subschemas; + + size_t c = 0; + for (const auto& subsch : sch.array_range()) + { + std::string sub_keys[] = { "allOf", std::to_string(c++) }; + subschemas.emplace_back(make_cross_draft_schema_validator(context, subsch, sub_keys, anchor_dict)); + } + return jsoncons::make_unique>(parent, std::move(schema_location), std::move(subschemas)); + } + + virtual std::unique_ptr> make_any_of_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.make_schema_location("anyOf"); + std::vector subschemas; + + size_t c = 0; + for (const auto& subsch : sch.array_range()) + { + std::string sub_keys[] = { "anyOf", std::to_string(c++) }; + subschemas.emplace_back(make_cross_draft_schema_validator(context, subsch, sub_keys, anchor_dict)); + } + return jsoncons::make_unique>(parent, std::move(schema_location), std::move(subschemas)); + } + + virtual std::unique_ptr> make_one_of_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location{ context.make_schema_location("oneOf") }; + std::vector subschemas; + + size_t c = 0; + for (const auto& subsch : sch.array_range()) + { + std::string sub_keys[] = { "oneOf", std::to_string(c++) }; + subschemas.emplace_back(make_cross_draft_schema_validator(context, subsch, sub_keys, anchor_dict)); + } + return jsoncons::make_unique>(parent, std::move(schema_location), std::move(subschemas)); + } + + virtual std::unique_ptr> make_dependencies_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::map dependent_required; + std::map dependent_schemas; + + //std::cout << "dependencies" << "\n" << pretty_print(sch) << "\n"; + + for (const auto& dep : sch.object_range()) + { + switch (dep.value().type()) + { + case json_type::array_value: + { + auto location = context.make_schema_location("dependencies"); + dependent_required.emplace(dep.key(), + this->make_required_validator(compilation_context(std::vector{{uri_wrapper{ location }}}), + dep.value(), sch)); + break; + } + case json_type::bool_value: + case json_type::object_value: + { + std::string sub_keys[] = {"dependencies"}; + dependent_schemas.emplace(dep.key(), + make_cross_draft_schema_validator(context, dep.value(), sub_keys, anchor_dict)); + break; + } + default: + { + break; + } + } + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(dependent_required), std::move(dependent_schemas)); + } + + virtual std::unique_ptr> make_property_names_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + schema_validator_type property_names_schema_validator; + + std::string sub_keys[] = { "propertyNames"}; + property_names_schema_validator = make_cross_draft_schema_validator(context, sch, sub_keys, anchor_dict); + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(property_names_schema_validator)); + } + + // 201909 and later + + virtual std::unique_ptr> make_dependent_required_validator( + const compilation_context& context, const Json& sch, const Json& parent) + { + uri schema_location = context.get_base_uri(); + std::map dependent_required; + + for (const auto& dep : sch.object_range()) + { + switch (dep.value().type()) + { + case json_type::array_value: + { + auto location = context.make_schema_location("dependentRequired"); + dependent_required.emplace(dep.key(), + this->make_required_validator(compilation_context(std::vector{{uri_wrapper{ location }}}), + dep.value(), sch)); + break; + } + default: + { + break; + } + } + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(dependent_required)); + } + + virtual std::unique_ptr> make_dependent_schemas_validator( const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::map dependent_schemas; + + for (const auto& dep : sch.object_range()) + { + switch (dep.value().type()) + { + case json_type::bool_value: + case json_type::object_value: + { + std::string sub_keys[] = {"dependentSchemas"}; + dependent_schemas.emplace(dep.key(), + make_cross_draft_schema_validator(context, dep.value(), sub_keys, anchor_dict)); + break; + } + default: + { + break; + } + } + } + + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(dependent_schemas)); + } + + std::unique_ptr> make_prefix_items_validator_07(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + std::vector prefix_item_validators; + std::unique_ptr> items_val; + + uri schema_location{context.make_schema_location("items")}; + + if (sch.type() == json_type::array_value) + { + size_t c = 0; + for (const auto& subsch : sch.array_range()) + { + std::string sub_keys[] = {"items", std::to_string(c++)}; + + prefix_item_validators.emplace_back(this->make_cross_draft_schema_validator(context, subsch, sub_keys, anchor_dict)); + } + + auto it = parent.find("additionalItems"); + if (it != parent.object_range().end()) + { + uri items_location{context.make_schema_location("additionalItems")}; + std::string sub_keys[] = {"additionalItems"}; + items_val = jsoncons::make_unique>("additionalItems", parent, items_location, + this->make_cross_draft_schema_validator(context, it->value(), sub_keys, anchor_dict)); + } + } + + return jsoncons::make_unique>("items", parent, schema_location, + std::move(prefix_item_validators), std::move(items_val)); + } + + std::unique_ptr> make_items_validator(const std::string& keyword_name, + const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location{context.make_schema_location(keyword_name)}; + + std::string sub_keys[] = {keyword_name}; + + return jsoncons::make_unique>(keyword_name, parent, schema_location, + this->make_cross_draft_schema_validator(context, sch, sub_keys, anchor_dict)); + } + + virtual std::unique_ptr> make_unevaluated_properties_validator( + const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + + std::string sub_keys[] = {"unevaluatedProperties"}; + + return jsoncons::make_unique>(parent, std::move(schema_location), + make_cross_draft_schema_validator(context, sch, sub_keys, anchor_dict)); + } + + virtual std::unique_ptr> make_unevaluated_items_validator( + const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + + std::string sub_keys[] = {"unevaluatedItems"}; + + return jsoncons::make_unique>(parent, std::move(schema_location), + make_cross_draft_schema_validator(context, sch, sub_keys, anchor_dict)); + } + + std::unique_ptr> make_additional_properties_validator( + const compilation_context& context, const Json& sch, const Json& parent, + std::unique_ptr>&& properties, std::unique_ptr>&& pattern_properties, + anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::vector validators; + schema_validator_type additional_properties; + + std::string sub_keys[] = {"additionalProperties"}; + additional_properties = this->make_cross_draft_schema_validator(context, sch, sub_keys, anchor_dict); + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(properties), std::move(pattern_properties), + std::move(additional_properties)); + } + + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_COMMON_SCHEMA_HPP diff --git a/third_party/jsoncons_ext/jsonschema/common/schema_validators.hpp b/third_party/jsoncons_ext/jsonschema/common/schema_validators.hpp new file mode 100644 index 0000000000..608193ed0d --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/schema_validators.hpp @@ -0,0 +1,467 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_SCHEMA_VALIDATORS_HPP +#define JSONCONS_JSONSCHEMA_COMMON_SCHEMA_VALIDATORS_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + template + class document_schema_validator : public schema_validator + { + using keyword_validator_type = std::unique_ptr>; + using schema_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::unique_ptr root_schema_; + schema_validator_type schema_val_; + std::vector schemas_; + public: + document_schema_validator(std::unique_ptr&& root_schema, schema_validator_type&& schema_val, std::vector&& schemas) + : root_schema_(std::move(root_schema)), schema_val_(std::move(schema_val)), schemas_(std::move(schemas)) + { + if (schema_val_ == nullptr) + JSONCONS_THROW(schema_error("There is no schema to validate an instance against")); + } + + document_schema_validator(const document_schema_validator&) = delete; + document_schema_validator(document_schema_validator&&) = default; + document_schema_validator& operator=(const document_schema_validator&) = delete; + document_schema_validator& operator=(document_schema_validator&&) = default; + + jsoncons::optional get_default_value() const final + { + return schema_val_->get_default_value(); + } + + const uri& schema_location() const final + { + return schema_val_->schema_location(); + } + + bool recursive_anchor() const final + { + return schema_val_->recursive_anchor(); + } + + const jsoncons::optional& id() const final + { + return schema_val_->id(); + } + + const jsoncons::optional& dynamic_anchor() const final + { + return schema_val_->dynamic_anchor(); + } + + const schema_validator* get_schema_for_dynamic_anchor(const std::string& anchor) const final + { + return schema_val_->get_schema_for_dynamic_anchor(anchor); + } + + bool always_fails() const final + { + return schema_val_->always_fails(); + } + + bool always_succeeds() const final + { + return schema_val_->always_succeeds(); + } + + walk_result walk(const evaluation_context& context, + const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const + { + return do_walk(context, instance, instance_location, reporter); + } + + private: + walk_result do_validate(const evaluation_context& context, + const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const + { + JSONCONS_ASSERT(schema_val_ != nullptr); + return schema_val_->validate(context, instance, instance_location, results, reporter, patch); + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + JSONCONS_ASSERT(schema_val_ != nullptr); + return schema_val_->walk(context, instance, instance_location, reporter); + } + }; + + template + class boolean_schema_validator : public schema_validator + { + public: + using schema_validator_type = typename std::unique_ptr>; + using keyword_validator_type = typename std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + uri schema_location_; + bool value_; + + jsoncons::optional id_; + + jsoncons::optional dynamic_anchor_; + + public: + boolean_schema_validator(const boolean_schema_validator&) = delete; + boolean_schema_validator& operator=(const boolean_schema_validator&) = delete; + boolean_schema_validator(boolean_schema_validator&&) = default; + boolean_schema_validator& operator=(boolean_schema_validator&&) = default; + boolean_schema_validator(const uri& schema_location, bool value) + : schema_location_(schema_location), value_(value) + { + } + + jsoncons::optional get_default_value() const final + { + return jsoncons::optional{}; + } + + const uri& schema_location() const final + { + return schema_location_; + } + + bool recursive_anchor() const final + { + return false; + } + + const jsoncons::optional& id() const final + { + return id_; + } + + const jsoncons::optional& dynamic_anchor() const final + { + return dynamic_anchor_; + } + + const schema_validator* get_schema_for_dynamic_anchor(const std::string& /*anchor*/) const final + { + return nullptr; + } + + bool always_fails() const final + { + return !value_; + } + + bool always_succeeds() const final + { + return value_; + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json&, + const jsonpointer::json_pointer& instance_location, + evaluation_results& /*results*/, + error_reporter& reporter, + Json& /*patch*/) const final + { + if (!value_) + { + reporter.error(validation_message("false", + context.eval_path(), + this->schema_location(), + instance_location, + "False schema always fails")); + } + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& /*context*/, const Json& /*instance*/, + const jsonpointer::json_pointer& /*instance_location*/, const walk_reporter_type& /*reporter*/) const final + { + return walk_result::advance; + } + }; + + template + class object_schema_validator : public schema_validator + { + public: + using schema_validator_type = typename std::unique_ptr>; + using keyword_validator_type = typename std::unique_ptr>; + using anchor_schema_map_type = std::unordered_map>>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + uri schema_location_; + jsoncons::optional id_; + std::vector validators_; + std::unique_ptr> unevaluated_properties_val_; + std::unique_ptr> unevaluated_items_val_; + std::map defs_; + Json default_value_; + bool recursive_anchor_; + jsoncons::optional dynamic_anchor_; + anchor_schema_map_type anchor_dict_; + bool always_succeeds_; + bool always_fails_; + + public: + object_schema_validator(const object_schema_validator&) = delete; + object_schema_validator& operator=(const object_schema_validator&) = delete; + object_schema_validator(object_schema_validator&&) = default; + object_schema_validator& operator=(object_schema_validator&&) = default; + object_schema_validator(const uri& schema_location, + const jsoncons::optional& id, + std::vector&& validators, + std::map&& defs, + Json&& default_value) + : schema_location_(schema_location), + id_(id), + validators_(std::move(validators)), + defs_(std::move(defs)), + default_value_(std::move(default_value)), + recursive_anchor_(false), + always_succeeds_(false), always_fails_(false) + { + init(); + } + object_schema_validator(const uri& schema_location, + const jsoncons::optional& id, + std::vector&& validators, + std::unique_ptr>&& unevaluated_properties_val, + std::unique_ptr>&& unevaluated_items_val, + std::map&& defs, + Json&& default_value, bool recursive_anchor) + : schema_location_(schema_location), + id_(id), + validators_(std::move(validators)), + unevaluated_properties_val_(std::move(unevaluated_properties_val)), + unevaluated_items_val_(std::move(unevaluated_items_val)), + defs_(std::move(defs)), + default_value_(std::move(default_value)), + recursive_anchor_(recursive_anchor), + always_succeeds_(false), always_fails_(false) + { + init(); + } + object_schema_validator(const uri& schema_location, + const jsoncons::optional& id, + std::vector&& validators, + std::unique_ptr>&& unevaluated_properties_val, + std::unique_ptr>&& unevaluated_items_val, + std::map&& defs, + Json&& default_value, + jsoncons::optional&& dynamic_anchor, + anchor_schema_map_type&& anchor_dict) + : schema_location_(schema_location), + id_(std::move(id)), + validators_(std::move(validators)), + unevaluated_properties_val_(std::move(unevaluated_properties_val)), + unevaluated_items_val_(std::move(unevaluated_items_val)), + defs_(std::move(defs)), + default_value_(std::move(default_value)), + recursive_anchor_(false), + dynamic_anchor_(std::move(dynamic_anchor)), + anchor_dict_(std::move(anchor_dict)), + always_succeeds_(false), always_fails_(false) + { + init(); + } + + jsoncons::optional get_default_value() const override + { + return default_value_; + } + + const uri& schema_location() const override + { + return schema_location_; + } + + bool recursive_anchor() const final + { + return recursive_anchor_; + } + + const jsoncons::optional& id() const final + { + return id_; + } + + const schema_validator* get_schema_for_dynamic_anchor(const std::string& anchor) const final + { + auto it = anchor_dict_.find(anchor); + return (it == anchor_dict_.end()) ? nullptr : it->second->referred_schema(); + } + + const jsoncons::optional& dynamic_anchor() const final + { + return dynamic_anchor_; + } + + bool always_fails() const final + { + return always_fails_; + } + + bool always_succeeds() const final + { + return always_succeeds_; + } + + private: + + void init() + { + if (!(unevaluated_properties_val_ || unevaluated_items_val_)) + { + std::size_t always_fails_count = 0; + std::size_t always_succeeds_count = 0; + for (const auto& val : validators_) + { + if (val->always_fails()) + { + ++always_fails_count; + } + if (val->always_succeeds()) + { + ++always_succeeds_count; + } + } + always_succeeds_ = always_succeeds_count == validators_.size(); // empty schema always succeeds + always_fails_ = validators_.size() > 0 && (always_fails_count == validators_.size()); + } + } + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + //std::cout << "object_schema_validator begin[" << context.eval_path().string() << "," << this->schema_location().string() << "]"; + //std::cout << "results:\n"; + //for (const auto& s : results) + //{ + // std::cout << " " << s << "\n"; + //} + //std::cout << "\n"; + + evaluation_results local_results; + + evaluation_flags flags = context.eval_flags(); + if (unevaluated_properties_val_) + { + flags |= evaluation_flags::require_evaluated_properties; + } + if (unevaluated_items_val_) + { + flags |= evaluation_flags::require_evaluated_items; + } + + evaluation_context this_context{context, this, flags}; + + //std::cout << "validators:\n"; + for (auto& val : validators_) + { + //std::cout << " " << val->keyword_name() << "\n"; + walk_result result = val->validate(this_context, instance, instance_location, local_results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + } + + if (unevaluated_properties_val_) + { + walk_result result = unevaluated_properties_val_->validate(this_context, instance, instance_location, local_results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + } + + if (unevaluated_items_val_) + { + walk_result result = unevaluated_items_val_->validate(this_context, instance, instance_location, local_results, reporter, patch); + if (result == walk_result::abort) + { + return result; + } + } + + if ((context.eval_flags() & evaluation_flags::require_evaluated_properties) + == evaluation_flags::require_evaluated_properties) + { + results.merge(std::move(local_results.evaluated_properties)); + } + if ((context.eval_flags() & evaluation_flags::require_evaluated_items) + == evaluation_flags::require_evaluated_items) + { + results.merge(std::move(local_results.evaluated_items)); + } + + //std::cout << "object_schema_validator end[" << context.eval_path().string() << "," << this->schema_location().string() << "]"; + //std::cout << "results:\n"; + //for (const auto& s : results) + //{ + // std::cout << " " << s << "\n"; + //} + //std::cout << "\n"; + return walk_result::advance; + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + evaluation_context this_context{context, this}; + for (auto& val : validators_) + { + //std::cout << " " << val->keyword_name() << "\n"; + walk_result result = val->walk(this_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + if (unevaluated_properties_val_) + { + walk_result result = unevaluated_properties_val_->walk(this_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + + if (unevaluated_items_val_) + { + walk_result result = unevaluated_items_val_->walk(this_context, instance, instance_location, reporter); + if (result == walk_result::abort) + { + return result; + } + } + return walk_result::advance; + } + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_KEYWORD_VALIDATOR_HPP diff --git a/third_party/jsoncons_ext/jsonschema/common/uri_wrapper.hpp b/third_party/jsoncons_ext/jsonschema/common/uri_wrapper.hpp new file mode 100644 index 0000000000..8816814dd1 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/uri_wrapper.hpp @@ -0,0 +1,180 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_SCHEMA_IDENTIFIER_HPP +#define JSONCONS_JSONSCHEMA_COMMON_SCHEMA_IDENTIFIER_HPP + +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + class uri_wrapper + { + jsoncons::uri uri_; + std::string identifier_; + bool has_plain_name_fragment_; + public: + uri_wrapper() + : has_plain_name_fragment_(false) + { + } + + explicit uri_wrapper(const std::string& uri) + { + uri_ = jsoncons::uri(uri); + if (!uri_.encoded_fragment().empty()) + { + identifier_ = uri_.fragment(); + std::error_code ec; + jsonpointer::json_pointer::parse(identifier_, ec); + has_plain_name_fragment_ = ec ? true : false; + } + else + { + has_plain_name_fragment_ = false; + } + } + + explicit uri_wrapper(const uri& uri) + : uri_{uri} + { + uri_ = jsoncons::uri(uri); + if (!uri_.encoded_fragment().empty()) + { + identifier_ = uri_.fragment(); + std::error_code ec; + jsonpointer::json_pointer::parse(identifier_, ec); + has_plain_name_fragment_ = ec ? true : false; + } + else + { + has_plain_name_fragment_ = false; + } + } + + const jsoncons::uri& uri() const + { + return uri_; + } + + bool has_fragment() const + { + return !uri_.encoded_fragment().empty(); + } + + bool has_plain_name_fragment() const + { + return has_plain_name_fragment_; + } + + jsoncons::uri base() const + { + return uri_.base(); + } + + std::string path() const + { + return uri_.path(); + } + + bool is_absolute() const + { + return uri_.is_absolute(); + } + + std::string fragment() const + { + return identifier_; + } + + uri_wrapper resolve(const uri_wrapper& uri) const + { + return uri_wrapper(uri_.resolve(uri.uri_)); + } + + int compare(const uri_wrapper& other) const + { + int result = uri_.compare(other.uri_); + if (result != 0) + { + return result; + } + return result; + } + + uri_wrapper append(const std::string& field) const + { + if (has_plain_name_fragment()) + return *this; + + jsoncons::jsonpointer::json_pointer pointer(std::string(uri_.encoded_fragment())); + pointer /= field; + + jsoncons::uri new_uri(uri_, uri_fragment_part, pointer.to_string()); + + return uri_wrapper(std::move(new_uri)); + } + + uri_wrapper append(std::size_t index) const + { + if (has_plain_name_fragment()) + return *this; + + jsoncons::jsonpointer::json_pointer pointer(std::string(uri_.encoded_fragment())); + pointer /= index; + + jsoncons::uri new_uri(uri_, uri_fragment_part, pointer.to_string()); + + return uri_wrapper(std::move(new_uri)); + } + + std::string string() const + { + std::string s = uri_.string(); + return s; + } + + friend bool operator==(const uri_wrapper& lhs, const uri_wrapper& rhs) + { + return lhs.compare(rhs) == 0; + } + + friend bool operator!=(const uri_wrapper& lhs, const uri_wrapper& rhs) + { + return lhs.compare(rhs) != 0; + } + + friend bool operator<(const uri_wrapper& lhs, const uri_wrapper& rhs) + { + return lhs.compare(rhs) < 0; + } + + friend bool operator<=(const uri_wrapper& lhs, const uri_wrapper& rhs) + { + return lhs.compare(rhs) <= 0; + } + + friend bool operator>(const uri_wrapper& lhs, const uri_wrapper& rhs) + { + return lhs.compare(rhs) > 0; + } + + friend bool operator>=(const uri_wrapper& lhs, const uri_wrapper& rhs) + { + return lhs.compare(rhs) >= 0; + } + private: + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_LOCATION_HPP diff --git a/third_party/jsoncons_ext/jsonschema/common/validator.hpp b/third_party/jsoncons_ext/jsonschema/common/validator.hpp new file mode 100644 index 0000000000..1810e72e4f --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/common/validator.hpp @@ -0,0 +1,438 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_COMMON_VALIDATOR_HPP +#define JSONCONS_JSONSCHEMA_COMMON_VALIDATOR_HPP + +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + enum class walk_result {advance, abort}; + + template + struct json_schema_traits + { + using walk_reporter_type = std::function; + }; + + // Interface for validation error handlers + class error_reporter + { + std::size_t error_count_; + public: + error_reporter() + : error_count_(0) + { + } + + virtual ~error_reporter() = default; + + walk_result error(const validation_message& msg) + { + ++error_count_; + return do_error(msg); + } + + std::size_t error_count() const + { + return error_count_; + } + + private: + virtual walk_result do_error(const validation_message& /* e */) = 0; + }; + + struct collecting_error_listener : public error_reporter + { + std::vector errors; + + private: + walk_result do_error(const validation_message& msg) final + { + errors.push_back(msg); + return walk_result::advance; + } + }; + + class range + { + std::size_t start_; + std::size_t end_; + public: + range() + : start_(0), end_(0) + { + } + + range(std::size_t start, std::size_t end) + : start_(start), end_(end) + { + } + + std::size_t start() const + { + return start_; + } + + std::size_t end() const + { + return end_; + } + + bool contains(std::size_t index) const + { + return index >= start_ && index < end_; + } + }; + + class range_collection + { + std::vector ranges_; + public: + using const_iterator = std::vector::const_iterator; + using value_type = range; + + range_collection() + { + } + range_collection(const range_collection& other) = default; + range_collection(range_collection&& other) = default; + + range_collection& operator=(const range_collection& other) = default; + range_collection& operator=(range_collection&& other) = default; + + std::size_t size() const + { + return ranges_.size(); + } + + range operator[](std::size_t index) const + { + return ranges_[index]; + } + + const_iterator begin() const + { + return ranges_.cbegin(); + } + + const_iterator end() const + { + return ranges_.cend(); + } + + void insert(range index_range) + { + ranges_.push_back(index_range); + } + + bool contains(std::size_t index) const + { + bool found = false; + std::size_t length = ranges_.size(); + for (std::size_t i = 0; i < length && !found; ++i) + { + if (ranges_[i].contains(index)) + { + found = true; + } + } + return found; + } + }; + + struct evaluation_results + { + std::unordered_set evaluated_properties; + range_collection evaluated_items; + + void merge(const evaluation_results& results) + { + for (auto&& name : results.evaluated_properties) + { + evaluated_properties.insert(name); + } + for (auto index_range : results.evaluated_items) + { + evaluated_items.insert(index_range); + } + } + void merge(std::unordered_set&& properties) + { + for (auto&& name : properties) + { + evaluated_properties.insert(std::move(name)); + } + } + void merge(const range_collection& ranges) + { + for (auto index_range : ranges) + { + evaluated_items.insert(index_range); + } + } + }; + + template + class schema_validator; + + template + class ref + { + public: + virtual ~ref() = default; + virtual void set_referred_schema(const schema_validator* target) = 0; + }; + + template + class validator_base + { + public: + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + virtual ~validator_base() = default; + + virtual const uri& schema_location() const = 0; + + walk_result validate(const evaluation_context& context, + const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const + { + return do_validate(context, instance, instance_location, results, reporter, patch); + } + + walk_result walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const + { + return do_walk(context, instance, instance_location, reporter); + } + + private: + virtual walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const = 0; + + virtual walk_result do_walk(const evaluation_context& /*context*/, const Json& /*instance*/, + const jsonpointer::json_pointer& /*instance_location*/, const walk_reporter_type& /*reporter*/) const = 0; + }; + + template + class keyword_validator : public validator_base + { + public: + using keyword_validator_type = std::unique_ptr>; + + virtual const std::string& keyword_name() const = 0; + + virtual bool always_fails() const + { + return false; + } + + virtual bool always_succeeds() const + { + return false; + } + }; + + template + class keyword_validator_base : public keyword_validator + { + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::string keyword_name_; + const Json* schema_ptr_; + uri schema_location_; + public: + using keyword_validator_type = std::unique_ptr>; + + keyword_validator_base(const std::string& keyword_name, const Json& schema, const uri& schema_location) + : keyword_name_(keyword_name), schema_ptr_(std::addressof(schema)), schema_location_(schema_location) + { + } + + keyword_validator_base(const keyword_validator_base&) = delete; + keyword_validator_base(keyword_validator_base&&) = default; + keyword_validator_base& operator=(const keyword_validator_base&) = delete; + keyword_validator_base& operator=(keyword_validator_base&&) = default; + + const std::string& keyword_name() const final + { + return keyword_name_; + } + + const Json& schema() const + { + JSONCONS_ASSERT(schema_ptr_ != nullptr); + return *schema_ptr_; + } + + const uri& schema_location() const final + { + return schema_location_; + } + }; + + template + class ref_validator : public keyword_validator_base, public virtual ref + { + using keyword_validator_type = std::unique_ptr>; + using schema_validator_type = std::unique_ptr>; + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + const schema_validator* referred_schema_; + + public: + ref_validator(const Json& schema, const ref_validator& other) + : keyword_validator_base(other.keyword_name(), schema, other.schema_location()), + referred_schema_{other.referred_schema_} + { + } + + ref_validator(const Json& schema, const uri& schema_location) + : keyword_validator_base("$ref", schema, schema_location), referred_schema_{nullptr} + { + //std::cout << "ref_validator: " << this->schema_location().string() << "\n"; + } + + ref_validator(const Json& schema, const uri& schema_location, const schema_validator* referred_schema) + : keyword_validator_base("$ref", schema, schema_location), referred_schema_(referred_schema) + { + //std::cout << "ref_validator2: " << this->schema_location().string() << "\n"; + } + + const schema_validator* referred_schema() const {return referred_schema_;} + + void set_referred_schema(const schema_validator* target) final { referred_schema_ = target; } + + uri get_base_uri() const + { + return this->schema_location(); + } + + private: + + walk_result do_validate(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, + evaluation_results& results, + error_reporter& reporter, + Json& patch) const final + { + evaluation_context this_context(context, this->keyword_name()); + + if (!referred_schema_) + { + return reporter.error(validation_message(this->keyword_name(), + this_context.eval_path(), + this->schema_location(), + instance_location, + "Unresolved schema reference " + this->schema_location().string())); + } + + return referred_schema_->validate(this_context, instance, instance_location, results, reporter, patch); + } + + walk_result do_walk(const evaluation_context& context, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const final + { + if (!referred_schema_) + { + return walk_result::advance; + } + evaluation_context this_context(context, this->keyword_name()); + return referred_schema_->walk(this_context, instance, instance_location, reporter); + } + }; + + template + class keyword_base + { + using walk_reporter_type = typename json_schema_traits::walk_reporter_type; + + std::string keyword_name_; + const Json* schema_ptr_; + uri schema_location_; + public: + + keyword_base(const std::string& keyword_name, const Json& schema, const uri& schema_location) + : keyword_name_(keyword_name), schema_ptr_(std::addressof(schema)), schema_location_(schema_location) + { + } + + virtual ~keyword_base() = default; + + keyword_base(const keyword_base&) = delete; + keyword_base(keyword_base&&) = default; + keyword_base& operator=(const keyword_base&) = delete; + keyword_base& operator=(keyword_base&&) = default; + + const std::string& keyword_name() const + { + return keyword_name_; + } + + const Json& schema() const + { + return *schema_ptr_; + } + + const uri& schema_location() const + { + return schema_location_; + } + + walk_result walk(const evaluation_context& /*context*/, const Json& instance, + const jsonpointer::json_pointer& instance_location, const walk_reporter_type& reporter) const + { + return reporter(this->keyword_name(), this->schema(), this->schema_location(), instance, instance_location); + } + }; + + template + class schema_validator : public validator_base + { + public: + using schema_validator_type = typename std::unique_ptr>; + using keyword_validator_type = typename std::unique_ptr>; + + public: + schema_validator() + {} + + virtual bool always_fails() const = 0; + + virtual bool always_succeeds() const = 0; + + virtual jsoncons::optional get_default_value() const = 0; + + virtual bool recursive_anchor() const = 0; + + virtual const jsoncons::optional& id() const = 0; + + virtual const schema_validator* get_schema_for_dynamic_anchor(const std::string& anchor) const = 0; + + virtual const jsoncons::optional& dynamic_anchor() const = 0; + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_KEYWORD_VALIDATOR_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft201909/schema_builder_201909.hpp b/third_party/jsoncons_ext/jsonschema/draft201909/schema_builder_201909.hpp new file mode 100644 index 0000000000..fb53086b6d --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft201909/schema_builder_201909.hpp @@ -0,0 +1,552 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_DRAFT201909_SCHEMA_BUILDER_201909_HPP +#define JSONCONS_JSONSCHEMA_DRAFT201909_SCHEMA_BUILDER_201909_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif + +namespace jsoncons { +namespace jsonschema { +namespace draft201909 { + + template + class schema_builder_201909 : public schema_builder + { + public: + using schema_store_type = typename schema_builder::schema_store_type; + using schema_builder_factory_type = typename schema_builder::schema_builder_factory_type; + using keyword_validator_type = typename std::unique_ptr>; + using schema_validator_type = typename std::unique_ptr>; + using recursive_ref_validator_type = recursive_ref_validator; + using anchor_uri_map_type = std::unordered_map; + + using keyword_factory_type = std::function; + + std::unordered_map validation_factory_map_; + + static const std::string& core_id() + { + static std::string id = "https://json-schema.org/draft/2019-09/vocab/core"; + return id; + } + static const std::string& applicator_id() + { + static std::string id = "https://json-schema.org/draft/2019-09/vocab/applicator"; + return id; + } + static const std::string& unevaluated_id() + { + static std::string id = "https://json-schema.org/draft/2019-09/vocab/unevaluated"; + return id; + } + static const std::string& validation_id() + { + static std::string id = "https://json-schema.org/draft/2019-09/vocab/validation"; + return id; + } + static const std::string& meta_data_id() + { + static std::string id = "https://json-schema.org/draft/2019-09/vocab/meta-data"; + return id; + } + static const std::string& format_annotation_id() + { + static std::string id = "https://json-schema.org/draft/2019-09/format-annotation"; + return id; + } + static const std::string& content_id() + { + static std::string id = "https://json-schema.org/draft/2019-09/vocab/content"; + return id; + } + + bool include_applicator_; + bool include_unevaluated_; + bool include_validation_; + bool include_format_; + + public: + schema_builder_201909(Json&& sch, const schema_builder_factory_type& builder_factory, + evaluation_options options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers, + const std::unordered_map& vocabulary) noexcept + : schema_builder(schema_version::draft201909(), + std::move(sch), builder_factory, options, schema_store_ptr, resolvers, vocabulary), + include_applicator_(true), include_unevaluated_(true), include_validation_(true), include_format_(true) + { + if (!vocabulary.empty()) + { + auto it = vocabulary.find(applicator_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_applicator_ = false; + } + it = vocabulary.find(unevaluated_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_unevaluated_ = false; + } + it = vocabulary.find(validation_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_validation_ = false; + } + it = vocabulary.find(format_annotation_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_format_ = false; + } + } + init(); + } + + schema_builder_201909(const schema_builder_201909&) = delete; + schema_builder_201909& operator=(const schema_builder_201909&) = delete; + schema_builder_201909(schema_builder_201909&&) = default; + schema_builder_201909& operator=(schema_builder_201909&&) = default; + + void init() + { + validation_factory_map_.emplace("type", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_type_validator(context, sch, parent);}); +/* + validation_factory_map_.emplace("contentEncoding", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_encoding_validator(context, sch, parent);}); + validation_factory_map_.emplace("contentMediaType", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_media_type_validator(context, sch, parent);}); +*/ +#if defined(JSONCONS_HAS_STD_REGEX) + validation_factory_map_.emplace("pattern", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_pattern_validator(context, sch, parent);}); +#endif + validation_factory_map_.emplace("maxItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_items_validator(context, sch, parent);}); + validation_factory_map_.emplace("minItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_items_validator(context, sch, parent);}); + validation_factory_map_.emplace("maxProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_properties_validator(context, sch, parent);}); + validation_factory_map_.emplace("minProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_properties_validator(context, sch, parent);}); + validation_factory_map_.emplace("contains", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + {return this->make_contains_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("uniqueItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_unique_items_validator(context, sch, parent);}); + validation_factory_map_.emplace("maxLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_length_validator(context, sch, parent);}); + validation_factory_map_.emplace("minLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_length_validator(context, sch, parent);}); + validation_factory_map_.emplace("not", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_not_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("maximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_maximum_validator(context, sch, parent);}); + validation_factory_map_.emplace("exclusiveMaximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_maximum_validator(context, sch, parent);}); + validation_factory_map_.emplace("minimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_minimum_validator(context, sch, parent);}); + validation_factory_map_.emplace("exclusiveMinimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_minimum_validator(context, sch, parent);}); + validation_factory_map_.emplace("multipleOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_multiple_of_validator(context, sch, parent);}); + validation_factory_map_.emplace("const", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_const_validator(context, sch, parent);}); + validation_factory_map_.emplace("enum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_enum_validator(context, sch, parent);}); + validation_factory_map_.emplace("allOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_all_of_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("anyOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_any_of_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("oneOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_one_of_validator(context, sch, parent, anchor_dict);}); + if (this->options().compatibility_mode()) + { + validation_factory_map_.emplace("dependencies", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_dependencies_validator(context, sch, parent, anchor_dict);}); + } + validation_factory_map_.emplace("required", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_required_validator(context, sch, parent);}); + validation_factory_map_.emplace("dependentRequired", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_dependent_required_validator(context, sch, parent);}); + } + + schema_validator_type make_schema_validator(const compilation_context& context, + const Json& sch, jsoncons::span keys, anchor_uri_map_type& anchor_dict) override + { + auto new_context = make_compilation_context(context, sch, keys); + //std::cout << "make_schema_validator " << context.get_base_uri().string() << ", " << new_context.get_base_uri().string() << "\n\n"; + + schema_validator_type schema_validator_ptr; + + switch (sch.type()) + { + case json_type::bool_value: + { + schema_validator_ptr = this->make_boolean_schema(new_context, sch); + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + } + break; + } + case json_type::object_value: + { + std::set known_keywords; + + schema_validator_ptr = make_object_schema_validator(new_context, sch, anchor_dict); + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + /*for (const auto& item : sch.object_range()) + { + if (known_keywords.find(item.key()) == known_keywords.end()) + { + this->insert_unknown_keyword(uri, item.key(), item.value()); // save unknown keywords for later reference + } + }*/ + } + break; + } + default: + JSONCONS_THROW(schema_error("invalid JSON-type for a schema for " + new_context.get_base_uri().string() + ", expected: boolean or object")); + break; + } + + return schema_validator_ptr; + } + + schema_validator_type make_object_schema_validator( + const compilation_context& context, const Json& sch, anchor_uri_map_type& anchor_dict) + { + jsoncons::optional id = context.id(); + Json default_value{ jsoncons::null_type()}; + std::vector validators; + std::unique_ptr> unevaluated_properties_val; + std::unique_ptr> unevaluated_items_val; + std::set known_keywords; + bool recursive_anchor = false; + std::map defs; + + if (this->options().compatibility_mode()) + { + auto it = sch.find("definitions"); + if (it != sch.object_range().end()) + { + for (const auto& def : it->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + known_keywords.insert("definitions"); + } + } + auto it = sch.find("$defs"); + if (it != sch.object_range().end()) + { + for (const auto& def : it->value().object_range()) + { + std::string sub_keys[] = { "$defs", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + known_keywords.insert("$defs"); + } + + it = sch.find("$recursiveAnchor"); + if (it != sch.object_range().end()) + { + recursive_anchor = it->value().template as(); + } + + it = sch.find("default"); + if (it != sch.object_range().end()) + { + default_value = it->value(); + known_keywords.insert("default"); + } + + it = sch.find("$ref"); + if (it != sch.object_range().end()) // this schema has a reference + { + uri_wrapper relative(it->value().template as()); + auto resolved = relative.resolve(uri_wrapper{ context.get_base_uri() }); + validators.push_back(this->get_or_create_reference(sch, resolved)); + } + + it = sch.find("$recursiveRef"); + if (it != sch.object_range().end()) // this schema has a reference + { + uri_wrapper relative(it->value().template as()); + auto ref = relative.resolve(uri_wrapper + { context.get_base_uri()}); + auto orig = jsoncons::make_unique(sch, ref.uri().base()); + this->unresolved_refs_.emplace_back(ref.uri(), orig.get()); + validators.push_back(std::move(orig)); + } + + if (include_applicator_) + { + it = sch.find("propertyNames"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_property_names_validator(context, it->value(), sch, anchor_dict)); + } + + it = sch.find("dependentSchemas"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_dependent_schemas_validator(context, it->value(), sch, anchor_dict)); + } + + schema_validator_type if_validator; + schema_validator_type then_validator; + schema_validator_type else_validator; + + it = sch.find("if"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "if" }; + if_validator = make_schema_validator(context, it->value(), sub_keys, anchor_dict); + } + + it = sch.find("then"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "then" }; + then_validator = make_schema_validator(context, it->value(), sub_keys, anchor_dict); + } + + it = sch.find("else"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "else" }; + else_validator = make_schema_validator(context, it->value(), sub_keys, anchor_dict); + } + if (if_validator || then_validator || else_validator) + { + validators.emplace_back(jsoncons::make_unique>( + sch, context.get_base_uri(), + std::move(if_validator), std::move(then_validator), std::move(else_validator))); + } + + // Object validators + + std::unique_ptr> properties; + it = sch.find("properties"); + if (it != sch.object_range().end()) + { + properties = this->make_properties_validator(context, it->value(), sch, anchor_dict); + } + std::unique_ptr> pattern_properties; + + #if defined(JSONCONS_HAS_STD_REGEX) + it = sch.find("patternProperties"); + if (it != sch.object_range().end()) + { + pattern_properties = make_pattern_properties_validator(context, it->value(), sch, anchor_dict); + } + #endif + + it = sch.find("additionalProperties"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_additional_properties_validator(context, it->value(), sch, + std::move(properties), std::move(pattern_properties), anchor_dict)); + } + else + { + if (properties) + { + validators.emplace_back(std::move(properties)); + } + #if defined(JSONCONS_HAS_STD_REGEX) + if (pattern_properties) + { + validators.emplace_back(std::move(pattern_properties)); + } + #endif + } + + it = sch.find("items"); + if (it != sch.object_range().end()) + { + + if (it->value().type() == json_type::array_value) + { + validators.emplace_back(this->make_prefix_items_validator_07(context, it->value(), sch, anchor_dict)); + } + else if (it->value().type() == json_type::object_value || + it->value().type() == json_type::bool_value) + { + validators.emplace_back(this->make_items_validator("items", context, it->value(), sch, anchor_dict)); + } + } + } + if (include_validation_) + { + for (const auto& key_value : sch.object_range()) + { + auto factory_it = validation_factory_map_.find(key_value.key()); + if (factory_it != validation_factory_map_.end()) + { + auto validator = factory_it->second(context, key_value.value(), sch, anchor_dict); + if (validator) + { + validators.emplace_back(std::move(validator)); + } + } + } + } + + if (include_format_) + { + if (this->options().require_format_validation()) + { + it = sch.find("format"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_format_validator(context, it->value(), sch)); + } + } + } + if (include_unevaluated_) + { + it = sch.find("unevaluatedProperties"); + if (it != sch.object_range().end()) + { + unevaluated_properties_val = this->make_unevaluated_properties_validator(context, it->value(), sch, anchor_dict); + } + it = sch.find("unevaluatedItems"); + if (it != sch.object_range().end()) + { + unevaluated_items_val = this->make_unevaluated_items_validator(context, it->value(), sch, anchor_dict); + } + } + + return jsoncons::make_unique>(context.get_base_uri(), std::move(id), + std::move(validators), std::move(unevaluated_properties_val), std::move(unevaluated_items_val), + std::move(defs), std::move(default_value), recursive_anchor); + } + +#if defined(JSONCONS_HAS_STD_REGEX) + + std::unique_ptr> make_pattern_properties_validator( const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::vector> pattern_properties; + + for (const auto& prop : sch.object_range()) + { + std::string sub_keys[] = {prop.key()}; + pattern_properties.emplace_back( + std::make_pair( + std::regex(prop.key(), std::regex::ECMAScript), + make_schema_validator(context, prop.value(), sub_keys, anchor_dict))); + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(pattern_properties)); + } +#endif + + private: + + compilation_context make_compilation_context(const compilation_context& parent, + const Json& sch, jsoncons::span keys) const override + { + // Exclude uri's that are not plain name identifiers + std::vector new_uris; + for (const auto& uri : parent.uris()) + { + if (!uri.has_plain_name_fragment()) + { + new_uris.push_back(uri); + } + } + + // Append the keys for this sub-schema to the uri's + for (const auto& key : keys) + { + for (auto& uri : new_uris) + { + auto new_u = uri.append(key); + uri = uri_wrapper(new_u); + } + } + + jsoncons::optional id; + if (sch.is_object()) + { + auto it = sch.find("$id"); // If $id is found, this schema can be referenced by the id + if (it != sch.object_range().end()) + { + uri_wrapper relative(it->value().template as()); + if (relative.has_fragment()) + { + JSONCONS_THROW(schema_error("Draft 2019-09 does not allow $id with fragment")); + } + uri_wrapper new_uri = relative.resolve(uri_wrapper{ parent.get_base_uri() }); + id = new_uri.uri(); + //std::cout << "$id: " << id << ", " << new_uri.string() << "\n"; + // Add it to the list if it is not already there + if (std::find(new_uris.begin(), new_uris.end(), new_uri) == new_uris.end()) + { + new_uris.emplace_back(new_uri); + } + } + it = sch.find("$anchor"); + if (it != sch.object_range().end()) + { + auto anchor = it->value().template as(); + if (!this->validate_anchor(anchor)) + { + JSONCONS_THROW(schema_error("Invalid $anchor " + anchor)); + } + auto uri = !new_uris.empty() ? new_uris.back().uri() : jsoncons::uri{"#"}; + jsoncons::uri new_uri(uri, uri_fragment_part, anchor); + uri_wrapper identifier{ new_uri }; + if (std::find(new_uris.begin(), new_uris.end(), identifier) == new_uris.end()) + { + new_uris.emplace_back(std::move(identifier)); + } + } + } + +/* + std::cout << "Absolute URI: " << parent.get_base_uri().string() << "\n"; + for (const auto& uri : new_uris) + { + std::cout << " " << uri.string() << "\n"; + } +*/ + return compilation_context(new_uris, id); + } + + }; + +} // namespace draft201909 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_DRAFT7_KEYWORD_FACTORY_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft201909/schema_draft201909.hpp b/third_party/jsoncons_ext/jsonschema/draft201909/schema_draft201909.hpp new file mode 100644 index 0000000000..f45ce75d79 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft201909/schema_draft201909.hpp @@ -0,0 +1,336 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_SCHEMA_DRAFT201909_HPP +#define JSONCONS_JSONSCHEMA_SCHEMA_DRAFT201909_HPP + +#include + +namespace jsoncons { +namespace jsonschema { +namespace draft201909 { + + template + struct schema_draft201909 + { + static Json get_schema() + { + static Json sch = Json::parse(R"( +{ + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/schema", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true, + "https://json-schema.org/draft/2019-09/vocab/applicator": true, + "https://json-schema.org/draft/2019-09/vocab/validation": true, + "https://json-schema.org/draft/2019-09/vocab/meta-data": true, + "https://json-schema.org/draft/2019-09/vocab/format": false, + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Core and Validation specifications meta-schema", + "allOf": [ + { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/core": true + }, + "$recursiveAnchor": true, + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$anchor": { + "type": "string", + "pattern": "^[A-Za-z][-A-Za-z0-9.:_]*$" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveRef": { + "type": "string", + "format": "uri-reference" + }, + "$recursiveAnchor": { + "type": "boolean", + "default": false + }, + "$vocabulary": { + "type": "object", + "propertyNames": { + "type": "string", + "format": "uri" + }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": {"$recursiveRef": "#"}, + "default": {} + } + } + }, + { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/applicator": true + }, + "$recursiveAnchor": true, + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "additionalItems": {"$recursiveRef": "#"}, + "unevaluatedItems": {"$recursiveRef": "#"}, + "items": { + "anyOf": [{"$recursiveRef": "#"}, {"$ref": "#/$defs/schemaArray"}] + }, + "contains": {"$recursiveRef": "#"}, + "additionalProperties": {"$recursiveRef": "#"}, + "unevaluatedProperties": {"$recursiveRef": "#"}, + "properties": { + "type": "object", + "additionalProperties": {"$recursiveRef": "#"}, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": {"$recursiveRef": "#"}, + "propertyNames": {"format": "regex"}, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { + "$recursiveRef": "#" + } + }, + "propertyNames": {"$recursiveRef": "#"}, + "if": {"$recursiveRef": "#"}, + "then": {"$recursiveRef": "#"}, + "else": {"$recursiveRef": "#"}, + "allOf": {"$ref": "#/$defs/schemaArray"}, + "anyOf": {"$ref": "#/$defs/schemaArray"}, + "oneOf": {"$ref": "#/$defs/schemaArray"}, + "not": {"$recursiveRef": "#"} + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": {"$recursiveRef": "#"} + } + } + }, + { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/validation": true + }, + "$recursiveAnchor": true, + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": {"$ref": "#/$defs/nonNegativeInteger"}, + "minLength": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": {"$ref": "#/$defs/nonNegativeInteger"}, + "minItems": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": {"$ref": "#/$defs/nonNegativeInteger"}, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": {"$ref": "#/$defs/nonNegativeInteger"}, + "minProperties": {"$ref": "#/$defs/nonNegativeIntegerDefault0"}, + "required": {"$ref": "#/$defs/stringArray"}, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "type": { + "anyOf": [ + {"$ref": "#/$defs/simpleTypes"}, + { + "type": "array", + "items": {"$ref": "#/$defs/simpleTypes"}, + "minItems": 1, + "uniqueItems": true + } + ] + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": ["array", "boolean", "integer", "null", "number", "object", "string"] + }, + "stringArray": { + "type": "array", + "items": {"type": "string"}, + "uniqueItems": true, + "default": [] + } + } + }, + { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/meta-data": true + }, + "$recursiveAnchor": true, + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } + }, + { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/format", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/format": true + }, + "$recursiveAnchor": true, + + "title": "Format vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "format": {"type": "string"} + } + }, + { + "$schema": "https://json-schema.org/draft/2019-09/schema", + "$id": "https://json-schema.org/draft/2019-09/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2019-09/vocab/content": true + }, + "$recursiveAnchor": true, + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentMediaType": {"type": "string"}, + "contentEncoding": {"type": "string"}, + "contentSchema": {"$recursiveRef": "#"} + } + } + ], + "type": ["object", "boolean"], + "properties": { + "definitions": { + "$comment": "While no longer an official keyword as it is replaced by $defs, this keyword is retained in the meta-schema to prevent incompatible extensions as it remains in common use.", + "type": "object", + "additionalProperties": { "$recursiveRef": "#" }, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" is no longer a keyword, but schema authors should avoid redefining it to facilitate a smooth transition to \"dependentSchemas\" and \"dependentRequired\"", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$recursiveRef": "#" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + } + } + } +} + )"); + + return sch; + } + }; + +} // namespace draft201909 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft202012/schema_builder_202012.hpp b/third_party/jsoncons_ext/jsonschema/draft202012/schema_builder_202012.hpp new file mode 100644 index 0000000000..292413164c --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft202012/schema_builder_202012.hpp @@ -0,0 +1,679 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_DRAFT202012_SCHEMA_BUILDER_202012_HPP +#define JSONCONS_JSONSCHEMA_DRAFT202012_SCHEMA_BUILDER_202012_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif + +namespace jsoncons { +namespace jsonschema { +namespace draft202012 { + + template + class schema_builder_202012 : public schema_builder + { + public: + using schema_store_type = typename schema_builder::schema_store_type; + using schema_builder_factory_type = typename schema_builder::schema_builder_factory_type; + using keyword_validator_type = typename std::unique_ptr>; + using schema_validator_type = typename std::unique_ptr>; + using dynamic_ref_validator_type = dynamic_ref_validator; + using anchor_uri_map_type = std::unordered_map; + + using keyword_factory_type = std::function; + + std::unordered_map validation_factory_map_; + + static const std::string& core_id() + { + static std::string id = "https://json-schema.org/draft/2020-12/vocab/core"; + return id; + } + static const std::string& applicator_id() + { + static std::string id = "https://json-schema.org/draft/2020-12/vocab/applicator"; + return id; + } + static const std::string& unevaluated_id() + { + static std::string id = "https://json-schema.org/draft/2020-12/vocab/unevaluated"; + return id; + } + static const std::string& validation_id() + { + static std::string id = "https://json-schema.org/draft/2020-12/vocab/validation"; + return id; + } + static const std::string& meta_data_id() + { + static std::string id = "https://json-schema.org/draft/2020-12/vocab/meta-data"; + return id; + } + static const std::string& format_annotation_id() + { + static std::string id = "https://json-schema.org/draft/2020-12/format-annotation"; + return id; + } + static const std::string& content_id() + { + static std::string id = "https://json-schema.org/draft/2020-12/vocab/content"; + return id; + } + + bool include_applicator_; + bool include_unevaluated_; + bool include_validation_; + bool include_format_; + + public: + schema_builder_202012(Json&& sch, const schema_builder_factory_type& builder_factory, + evaluation_options options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers, + const std::unordered_map& vocabulary) + : schema_builder(schema_version::draft202012(), std::move(sch), + builder_factory, options, schema_store_ptr, resolvers, vocabulary), + include_applicator_(true), include_unevaluated_(true), include_validation_(true), include_format_(true) + { + if (!vocabulary.empty()) + { + auto it = vocabulary.find(applicator_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_applicator_ = false; + } + it = vocabulary.find(unevaluated_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_unevaluated_ = false; + } + it = vocabulary.find(validation_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_validation_ = false; + } + it = vocabulary.find(format_annotation_id()); + if (it == vocabulary.end() || !(it->second)) + { + include_format_ = false; + } + } + init(); + } + + schema_builder_202012(const schema_builder_202012&) = delete; + schema_builder_202012& operator=(const schema_builder_202012&) = delete; + schema_builder_202012(schema_builder_202012&&) = default; + schema_builder_202012& operator=(schema_builder_202012&&) = default; + + void init() + { + // validation + validation_factory_map_.emplace("type", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_type_validator(context, sch, parent);}); +/* + validation_factory_map_.emplace("contentEncoding", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_encoding_validator(context, sch, parent);}); + validation_factory_map_.emplace("contentMediaType", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_media_type_validator(context, sch, parent);}); +*/ +#if defined(JSONCONS_HAS_STD_REGEX) + validation_factory_map_.emplace("pattern", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_pattern_validator(context, sch, parent);}); +#endif + validation_factory_map_.emplace("maxItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_items_validator(context, sch, parent);}); + validation_factory_map_.emplace("minItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_items_validator(context, sch, parent);}); + validation_factory_map_.emplace("maxProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_properties_validator(context, sch, parent);}); + validation_factory_map_.emplace("minProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_properties_validator(context, sch, parent);}); + validation_factory_map_.emplace("contains", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + {return this->make_contains_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("uniqueItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_unique_items_validator(context, sch, parent);}); + validation_factory_map_.emplace("maxLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_length_validator(context, sch, parent);}); + validation_factory_map_.emplace("minLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_length_validator(context, sch, parent);}); + validation_factory_map_.emplace("not", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_not_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("maximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_maximum_validator(context, sch, parent);}); + validation_factory_map_.emplace("exclusiveMaximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_maximum_validator(context, sch, parent);}); + validation_factory_map_.emplace("minimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_minimum_validator(context, sch, parent);}); + validation_factory_map_.emplace("exclusiveMinimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_minimum_validator(context, sch, parent);}); + validation_factory_map_.emplace("multipleOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_multiple_of_validator(context, sch, parent);}); + validation_factory_map_.emplace("const", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_const_validator(context, sch, parent);}); + validation_factory_map_.emplace("enum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_enum_validator(context, sch, parent);}); + validation_factory_map_.emplace("allOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_all_of_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("anyOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_any_of_validator(context, sch, parent, anchor_dict);}); + validation_factory_map_.emplace("oneOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_one_of_validator(context, sch, parent, anchor_dict);}); + if (this->options().compatibility_mode()) + { + validation_factory_map_.emplace("dependencies", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_dependencies_validator(context, sch, parent, anchor_dict);}); + } + validation_factory_map_.emplace("required", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_required_validator(context, sch, parent);}); + validation_factory_map_.emplace("dependentRequired", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_dependent_required_validator(context, sch, parent);}); + } + + schema_validator_type make_schema_validator(const compilation_context& context, + const Json& sch, jsoncons::span keys, anchor_uri_map_type& anchor_dict) override + { + auto new_context = make_compilation_context(context, sch, keys); + //std::cout << "this->make_cross_draft_schema_validator " << context.get_base_uri().string() << ", " << new_context.get_base_uri().string() << "\n\n"; + + schema_validator_type schema_validator_ptr; + + switch (sch.type()) + { + case json_type::bool_value: + { + schema_validator_ptr = this->make_boolean_schema(new_context, sch); + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + } + break; + } + case json_type::object_value: + { + schema_validator_ptr = make_object_schema_validator(new_context, sch, anchor_dict); + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + /*for (const auto& item : sch.object_range()) + { + if (known_keywords().find(item.key()) == known_keywords().end()) + { + std::cout << " " << item.key() << "\n"; + this->insert_unknown_keyword(uri, item.key(), item.value()); // save unknown keywords for later reference + } + }*/ + } + break; + } + default: + JSONCONS_THROW(schema_error(new_context.get_base_uri().string() + ": Invalid JSON-type for a schema, expected: boolean or object")); + break; + } + + return schema_validator_ptr; + } + + schema_validator_type make_object_schema_validator(const compilation_context& context, + const Json& sch, anchor_uri_map_type& anchor_dict) + { + jsoncons::optional id = context.id(); + Json default_value{jsoncons::null_type()}; + std::vector validators; + std::unique_ptr> unevaluated_properties_val; + std::unique_ptr> unevaluated_items_val; + jsoncons::optional dynamic_anchor; + std::map defs; + anchor_uri_map_type local_anchor_dict; + + auto it = sch.find("$dynamicAnchor"); + if (it != sch.object_range().end()) + { + std::string value = it->value().template as(); + jsoncons::uri new_uri(context.get_base_uri(), uri_fragment_part, value); + dynamic_anchor = jsoncons::optional(new_uri); + local_anchor_dict.emplace(value, context.get_base_uri()); + } + + if (this->options().compatibility_mode()) + { + it = sch.find("definitions"); + if (it != sch.object_range().end()) + { + for (const auto& def : it->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), this->make_cross_draft_schema_validator(context, def.value(), sub_keys, local_anchor_dict)); + } + } + } + it = sch.find("$defs"); + if (it != sch.object_range().end()) + { + for (const auto& def : it->value().object_range()) + { + std::string sub_keys[] = { "$defs", def.key() }; + defs.emplace(def.key(), this->make_cross_draft_schema_validator(context, def.value(), sub_keys, local_anchor_dict)); + } + } + + it = sch.find("default"); + if (it != sch.object_range().end()) + { + default_value = it->value(); + } + + it = sch.find("$ref"); + if (it != sch.object_range().end()) // this schema has a reference + { + uri_wrapper relative(it->value().template as()); + auto ref = relative.resolve(uri_wrapper{ context.get_base_uri() }); + validators.push_back(this->get_or_create_reference(sch, ref)); + } + + it = sch.find("$dynamicRef"); + if (it != sch.object_range().end()) // this schema has a reference + { + std::string value = it->value().template as(); + uri_wrapper relative(value); + auto ref = relative.resolve(uri_wrapper{ context.get_base_uri() }); + auto orig = jsoncons::make_unique(sch, ref.uri().base(), ref); + this->unresolved_refs_.emplace_back(ref.uri(), orig.get()); + validators.push_back(std::move(orig)); + } + + if (include_applicator_) + { + it = sch.find("propertyNames"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_property_names_validator(context, it->value(), sch, local_anchor_dict)); + } + + it = sch.find("dependentSchemas"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_dependent_schemas_validator(context, it->value(), sch, local_anchor_dict)); + } + + schema_validator_type if_validator; + schema_validator_type then_validator; + schema_validator_type else_validator; + + it = sch.find("if"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "if" }; + if_validator = this->make_cross_draft_schema_validator(context, it->value(), sub_keys, local_anchor_dict); + } + + it = sch.find("then"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "then" }; + then_validator = this->make_cross_draft_schema_validator(context, it->value(), sub_keys, local_anchor_dict); + } + + it = sch.find("else"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "else" }; + else_validator = this->make_cross_draft_schema_validator(context, it->value(), sub_keys, local_anchor_dict); + } + if (if_validator || then_validator || else_validator) + { + validators.emplace_back(jsoncons::make_unique>( + sch, context.get_base_uri(), + std::move(if_validator), std::move(then_validator), std::move(else_validator))); + } + // Object validators + + std::unique_ptr> properties; + it = sch.find("properties"); + if (it != sch.object_range().end()) + { + properties = this->make_properties_validator(context, it->value(), sch, local_anchor_dict); + } + std::unique_ptr> pattern_properties; + + #if defined(JSONCONS_HAS_STD_REGEX) + it = sch.find("patternProperties"); + if (it != sch.object_range().end()) + { + pattern_properties = make_pattern_properties_validator(context, it->value(), sch, local_anchor_dict); + } + #endif + + it = sch.find("additionalProperties"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_additional_properties_validator(context, it->value(), sch, + std::move(properties), std::move(pattern_properties), local_anchor_dict)); + } + else + { + if (properties) + { + validators.emplace_back(std::move(properties)); + } + #if defined(JSONCONS_HAS_STD_REGEX) + if (pattern_properties) + { + validators.emplace_back(std::move(pattern_properties)); + } + #endif + } + + it = sch.find("prefixItems"); + if (it != sch.object_range().end()) + { + + if (it->value().type() == json_type::array_value) + { + validators.emplace_back(make_prefix_items_validator(context, it->value(), sch, local_anchor_dict)); + } + } + else + { + it = sch.find("items"); + if (it != sch.object_range().end()) + { + if (it->value().type() == json_type::object_value || it->value().type() == json_type::bool_value) + { + validators.emplace_back(this->make_items_validator("items", context, it->value(), sch, local_anchor_dict)); + } + } + } + } + + if (include_validation_) + { + for (const auto& key_value : sch.object_range()) + { + auto factory_it = validation_factory_map_.find(key_value.key()); + if (factory_it != validation_factory_map_.end()) + { + auto validator = factory_it->second(context, key_value.value(), sch, local_anchor_dict); + if (validator) + { + validators.emplace_back(std::move(validator)); + } + } + } + } + + if (include_format_) + { + if (this->options().require_format_validation()) + { + it = sch.find("format"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_format_validator(context, it->value(), sch)); + } + } + } + + if (include_unevaluated_) + { + it = sch.find("unevaluatedProperties"); + if (it != sch.object_range().end()) + { + unevaluated_properties_val = this->make_unevaluated_properties_validator(context, it->value(), sch, local_anchor_dict); + } + it = sch.find("unevaluatedItems"); + if (it != sch.object_range().end()) + { + unevaluated_items_val = this->make_unevaluated_items_validator(context, it->value(), sch, local_anchor_dict); + } + } + + if (!id) + { + for (const auto& member : local_anchor_dict) + { + anchor_dict[member.first] = member.second; + } + } + + std::unordered_map>> anchor_schema_map; + for (const auto& member : local_anchor_dict) + { + anchor_schema_map.emplace(member.first, this->get_or_create_reference(sch, member.second)); + } + return jsoncons::make_unique>(context.get_base_uri(), + std::move(id), + std::move(validators), std::move(unevaluated_properties_val), std::move(unevaluated_items_val), + std::move(defs), std::move(default_value), std::move(dynamic_anchor), std::move(anchor_schema_map)); + } + + std::unique_ptr> make_prefix_items_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + std::vector prefix_item_validators; + std::unique_ptr> items_val; + + uri schema_location{context.make_schema_location("prefixItems")}; + + if (sch.type() == json_type::array_value) + { + size_t c = 0; + for (const auto& subsch : sch.array_range()) + { + std::string sub_keys[] = {"prefixItems", std::to_string(c++)}; + + prefix_item_validators.emplace_back(this->make_cross_draft_schema_validator(context, subsch, sub_keys, anchor_dict)); + } + + auto it = parent.find("items"); + if (it != parent.object_range().end()) + { + uri items_location{context.make_schema_location("items")}; + std::string sub_keys[] = { "additionalItems" }; + + items_val = jsoncons::make_unique>("items", parent, items_location, + this->make_cross_draft_schema_validator(context, it->value(), sub_keys, anchor_dict)); + } + } + + return jsoncons::make_unique>("prefixItems", parent, schema_location, + std::move(prefix_item_validators), std::move(items_val)); + } + +#if defined(JSONCONS_HAS_STD_REGEX) + + std::unique_ptr> make_pattern_properties_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::vector> pattern_properties; + + for (const auto& prop : sch.object_range()) + { + std::string sub_keys[] = {prop.key()}; + pattern_properties.emplace_back( + std::make_pair( + std::regex(prop.key(), std::regex::ECMAScript), + this->make_cross_draft_schema_validator(context, prop.value(), sub_keys, anchor_dict))); + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(pattern_properties)); + } +#endif + + private: + + compilation_context make_compilation_context(const compilation_context& parent, + const Json& sch, jsoncons::span keys) const override + { + // Exclude uri's that are not plain name identifiers + std::vector new_uris; + for (const auto& uri : parent.uris()) + { + if (!uri.has_plain_name_fragment()) + { + new_uris.push_back(uri); + } + } + + // Append the keys for this sub-schema to the uri's + for (const auto& key : keys) + { + for (auto& uri : new_uris) + { + auto new_u = uri.append(key); + uri = uri_wrapper(new_u); + } + } + + jsoncons::optional id; + if (sch.is_object()) + { + auto it = sch.find("$id"); // If $id is found, this schema can be referenced by the id + if (it != sch.object_range().end()) + { + std::string str = it->value().template as(); + uri_wrapper relative(str); + if (relative.has_fragment()) + { + JSONCONS_THROW(schema_error(str + ": Draft 2019-09 does not allow $id with fragment")); + } + uri_wrapper new_uri = relative.resolve(uri_wrapper{ parent.get_base_uri() }); + id = new_uri.uri(); + //std::cout << "$id: " << id << ", " << new_uri.string() << "\n"; + // Add it to the list if it is not already there + if (std::find(new_uris.begin(), new_uris.end(), new_uri) == new_uris.end()) + { + new_uris.emplace_back(new_uri); + } + } + it = sch.find("$anchor"); + if (it != sch.object_range().end()) + { + auto anchor = it->value().template as(); + if (!this->validate_anchor(anchor)) + { + JSONCONS_THROW(schema_error("Invalid $anchor " + anchor)); + } + auto uri = !new_uris.empty() ? new_uris.back().uri() : jsoncons::uri{"#"}; + jsoncons::uri new_uri(uri, uri_fragment_part, anchor); + uri_wrapper identifier{ new_uri }; + if (std::find(new_uris.begin(), new_uris.end(), identifier) == new_uris.end()) + { + new_uris.emplace_back(std::move(identifier)); + } + } + it = sch.find("$dynamicAnchor"); + if (it != sch.object_range().end()) + { + auto anchor = it->value().template as(); + if (!this->validate_anchor(anchor)) + { + JSONCONS_THROW(schema_error("Invalid $dynamicAnchor " + anchor)); + } + auto uri = !new_uris.empty() ? new_uris.back().uri() : jsoncons::uri{"#"}; + jsoncons::uri new_uri(uri, uri_fragment_part, anchor); + uri_wrapper identifier{ new_uri }; + if (std::find(new_uris.begin(), new_uris.end(), identifier) == new_uris.end()) + { + new_uris.emplace_back(std::move(identifier)); + } + } + } + + //std::cout << "Absolute URI: " << parent.get_base_uri().string() << "\n"; + //for (const auto& uri : new_uris) + //{ + // std::cout << " " << uri.string() << "\n"; + //} + + return compilation_context(new_uris, id); + } + + private: + static const std::unordered_set& known_keywords() + { + static std::unordered_set keywords{ + "$anchor", + "$dynamicAnchor", + "$dynamicRef", + "$id", + "$ref", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "default", + "$defs", + "dependencies", + "dependentRequired", + "dependentSchemas", + "description", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "if", + "then", + "else", + "items", + "maximum", + "maxItems", + "maxLength", + "maxProperties", + "minimum", + "minItems", + "minLength", + "minProperties", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "prefixItems", + "properties", + "propertyNames", + "readOnly", + "required", + "title", + "type", + "uniqueItems", + "unevaluatedItems", + "unevaluatedProperties", + "writeOnly" + }; + return keywords; + } + }; + +} // namespace draft202012 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_DRAFT7_KEYWORD_FACTORY_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft202012/schema_draft202012.hpp b/third_party/jsoncons_ext/jsonschema/draft202012/schema_draft202012.hpp new file mode 100644 index 0000000000..dba86bb491 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft202012/schema_draft202012.hpp @@ -0,0 +1,364 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_SCHEMA_202012_HPP +#define JSONCONS_JSONSCHEMA_SCHEMA_202012_HPP + +#include + +namespace jsoncons { +namespace jsonschema { +namespace draft202012 { + + template + struct schema_draft202012 + { + static Json get_schema() + { + static Json sch = Json::parse(R"( +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/schema", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true, + "https://json-schema.org/draft/2020-12/vocab/applicator": true, + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true, + "https://json-schema.org/draft/2020-12/vocab/validation": true, + "https://json-schema.org/draft/2020-12/vocab/meta-data": true, + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true, + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Core and Validation specifications meta-schema", + "allOf": [ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/core", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/core": true + }, + "$dynamicAnchor": "meta", + + "title": "Core vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "$id": { + "$ref": "#/$defs/uriReferenceString", + "$comment": "Non-empty fragments not allowed.", + "pattern": "^[^#]*#?$" + }, + "$schema": { "$ref": "#/$defs/uriString" }, + "$ref": { "$ref": "#/$defs/uriReferenceString" }, + "$anchor": { "$ref": "#/$defs/anchorString" }, + "$dynamicRef": { "$ref": "#/$defs/uriReferenceString" }, + "$dynamicAnchor": { "$ref": "#/$defs/anchorString" }, + "$vocabulary": { + "type": "object", + "propertyNames": { "$ref": "#/$defs/uriString" }, + "additionalProperties": { + "type": "boolean" + } + }, + "$comment": { + "type": "string" + }, + "$defs": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" } + } + }, + "$defs": { + "anchorString": { + "type": "string", + "pattern": "^[A-Za-z_][-A-Za-z0-9._]*$" + }, + "uriString": { + "type": "string", + "format": "uri" + }, + "uriReferenceString": { + "type": "string", + "format": "uri-reference" + } + } +}, + +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/applicator", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/applicator": true + }, + "$dynamicAnchor": "meta", + + "title": "Applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "prefixItems": { "$ref": "#/$defs/schemaArray" }, + "items": { "$dynamicRef": "#meta" }, + "contains": { "$dynamicRef": "#meta" }, + "additionalProperties": { "$dynamicRef": "#meta" }, + "properties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependentSchemas": { + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "default": {} + }, + "propertyNames": { "$dynamicRef": "#meta" }, + "if": { "$dynamicRef": "#meta" }, + "then": { "$dynamicRef": "#meta" }, + "else": { "$dynamicRef": "#meta" }, + "allOf": { "$ref": "#/$defs/schemaArray" }, + "anyOf": { "$ref": "#/$defs/schemaArray" }, + "oneOf": { "$ref": "#/$defs/schemaArray" }, + "not": { "$dynamicRef": "#meta" } + }, + "$defs": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$dynamicRef": "#meta" } + } + } +}, +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/unevaluated", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/unevaluated": true + }, + "$dynamicAnchor": "meta", + + "title": "Unevaluated applicator vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "unevaluatedItems": { "$dynamicRef": "#meta" }, + "unevaluatedProperties": { "$dynamicRef": "#meta" } + } +}, +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/validation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/validation": true + }, + "$dynamicAnchor": "meta", + + "title": "Validation vocabulary meta-schema", + "type": ["object", "boolean"], + "properties": { + "type": { + "anyOf": [ + { "$ref": "#/$defs/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/$defs/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "const": true, + "enum": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/$defs/nonNegativeInteger" }, + "minLength": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "maxItems": { "$ref": "#/$defs/nonNegativeInteger" }, + "minItems": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxContains": { "$ref": "#/$defs/nonNegativeInteger" }, + "minContains": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 1 + }, + "maxProperties": { "$ref": "#/$defs/nonNegativeInteger" }, + "minProperties": { "$ref": "#/$defs/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/$defs/stringArray" }, + "dependentRequired": { + "type": "object", + "additionalProperties": { + "$ref": "#/$defs/stringArray" + } + } + }, + "$defs": { + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "$ref": "#/$defs/nonNegativeInteger", + "default": 0 + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + } +}, +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/meta-data", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/meta-data": true + }, + "$dynamicAnchor": "meta", + + "title": "Meta-data vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "deprecated": { + "type": "boolean", + "default": false + }, + "readOnly": { + "type": "boolean", + "default": false + }, + "writeOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + } + } +}, +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/format-annotation", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/format-annotation": true + }, + "$dynamicAnchor": "meta", + + "title": "Format vocabulary meta-schema for annotation results", + "type": ["object", "boolean"], + "properties": { + "format": { "type": "string" } + } +}, + { + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json-schema.org/draft/2020-12/meta/content", + "$vocabulary": { + "https://json-schema.org/draft/2020-12/vocab/content": true + }, + "$dynamicAnchor": "meta", + + "title": "Content vocabulary meta-schema", + + "type": ["object", "boolean"], + "properties": { + "contentEncoding": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentSchema": { "$dynamicRef": "#meta" } + } +} + ], + "type": ["object", "boolean"], + "$comment": "This meta-schema also defines keywords that have appeared in previous drafts in order to prevent incompatible extensions as they remain in common use.", + "properties": { + "definitions": { + "$comment": "\"definitions\" has been replaced by \"$defs\".", + "type": "object", + "additionalProperties": { "$dynamicRef": "#meta" }, + "deprecated": true, + "default": {} + }, + "dependencies": { + "$comment": "\"dependencies\" has been split and replaced by \"dependentSchemas\" and \"dependentRequired\" in order to serve their differing semantics.", + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$dynamicRef": "#meta" }, + { "$ref": "meta/validation#/$defs/stringArray" } + ] + }, + "deprecated": true, + "default": {} + }, + "$recursiveAnchor": { + "$comment": "\"$recursiveAnchor\" has been replaced by \"$dynamicAnchor\".", + "$ref": "meta/core#/$defs/anchorString", + "deprecated": true + }, + "$recursiveRef": { + "$comment": "\"$recursiveRef\" has been replaced by \"$dynamicRef\".", + "$ref": "meta/core#/$defs/uriReferenceString", + "deprecated": true + } + } +} + )"); + + return sch; + } + }; + +} // namespace draft202012 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft4/schema_builder_4.hpp b/third_party/jsoncons_ext/jsonschema/draft4/schema_builder_4.hpp new file mode 100644 index 0000000000..04d874e7b2 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft4/schema_builder_4.hpp @@ -0,0 +1,463 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_DRAFT4_SCHEMA_BUILDER_4_HPP +#define JSONCONS_JSONSCHEMA_DRAFT4_SCHEMA_BUILDER_4_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif + +namespace jsoncons { +namespace jsonschema { +namespace draft4 { + + template + class schema_builder_4 : public schema_builder + { + public: + using schema_store_type = typename schema_builder::schema_store_type; + using schema_builder_factory_type = typename schema_builder::schema_builder_factory_type; + using keyword_validator_type = typename std::unique_ptr>; + using schema_validator_type = typename std::unique_ptr>; + using anchor_uri_map_type = std::unordered_map; + private: + + using keyword_factory_type = std::function; + + std::unordered_map keyword_factory_map_; + + public: + schema_builder_4(Json&& sch, const schema_builder_factory_type& builder_factory, + evaluation_options options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers) + : schema_builder(schema_version::draft4(), std::move(sch), builder_factory, options, schema_store_ptr, resolvers) + { + init(); + } + + schema_builder_4(const schema_builder_4&) = delete; + schema_builder_4& operator=(const schema_builder_4&) = delete; + schema_builder_4(schema_builder_4&&) = default; + schema_builder_4& operator=(schema_builder_4&&) = default; + + void init() + { + keyword_factory_map_.emplace("type", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_type_validator(context, sch, parent);}); + keyword_factory_map_.emplace("contentEncoding", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_encoding_validator(context, sch, parent);}); + keyword_factory_map_.emplace("contentMediaType", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_media_type_validator(context, sch, parent);}); + if (this->options().require_format_validation()) + { + keyword_factory_map_.emplace("format", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_format_validator(context, sch, parent);}); + } +#if defined(JSONCONS_HAS_STD_REGEX) + keyword_factory_map_.emplace("pattern", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_pattern_validator(context, sch, parent);}); +#endif + keyword_factory_map_.emplace("maxItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("maxProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_properties_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_properties_validator(context, sch, parent);}); + keyword_factory_map_.emplace("uniqueItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_unique_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("maxLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_length_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_length_validator(context, sch, parent);}); + keyword_factory_map_.emplace("not", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_not_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("maximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return make_maximum_validator_4(context, sch, parent);}); + keyword_factory_map_.emplace("minimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return make_minimum_validator_4(context, sch, parent);}); + keyword_factory_map_.emplace("multipleOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_multiple_of_validator(context, sch, parent);}); + keyword_factory_map_.emplace("enum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_enum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("allOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_all_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("anyOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_any_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("oneOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_one_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("dependencies", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_dependencies_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("required", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_required_validator(context, sch, parent);}); + } + + schema_validator_type make_schema_validator( + const compilation_context& context, const Json& sch, jsoncons::span keys, + anchor_uri_map_type& anchor_dict) override + { + auto new_context = make_compilation_context(context, sch, keys); + //std::cout << "make_schema_validator " << context.get_base_uri().string() << ", " << new_context.get_base_uri().string() << "\n\n"; + + schema_validator_type schema_validator_ptr; + + switch (sch.type()) + { + case json_type::bool_value: + { + schema_validator_ptr = this->make_boolean_schema(new_context, sch); + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + } + break; + } + case json_type::object_value: + { + auto it = sch.find("$ref"); + if (it != sch.object_range().end()) // this schema is a reference + { + std::vector validators; + std::map defs; + + auto it2 = sch.find("definitions"); + if (it2 != sch.object_range().end()) + { + for (const auto& def : it2->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + } + + Json default_value{ jsoncons::null_type() }; + uri_wrapper relative(it->value().template as()); + auto id = relative.resolve(uri_wrapper{ context.get_base_uri() }); + validators.push_back(this->get_or_create_reference(sch, id)); + schema_validator_ptr = jsoncons::make_unique>( + new_context.get_base_uri(), context.id(), + std::move(validators), std::move(defs), std::move(default_value)); + } + else + { + schema_validator_ptr = make_object_schema_validator(new_context, sch, anchor_dict); + } + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + for (const auto& item : sch.object_range()) + { + if (known_keywords().find(item.key()) == known_keywords().end()) + { + this->insert_unknown_keyword(uri, item.key(), item.value()); // save unknown keywords for later reference + } + } + } + break; + } + default: + JSONCONS_THROW(schema_error("invalid JSON-type for a schema for " + new_context.get_base_uri().string() + ", expected: boolean or object")); + break; + } + + return schema_validator_ptr; + } + + schema_validator_type make_object_schema_validator(const compilation_context& context, + const Json& sch, anchor_uri_map_type& anchor_dict) + { + jsoncons::optional id = context.id(); + Json default_value{ jsoncons::null_type() }; + std::vector validators; + std::map defs; + + auto it = sch.find("definitions"); + if (it != sch.object_range().end()) + { + for (const auto& def : it->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + } + + it = sch.find("default"); + if (it != sch.object_range().end()) + { + default_value = it->value(); + } + + for (const auto& key_value : sch.object_range()) + { + auto factory_it = keyword_factory_map_.find(key_value.key()); + if (factory_it != keyword_factory_map_.end()) + { + auto validator = factory_it->second(context, key_value.value(), sch, anchor_dict); + if (validator) + { + validators.emplace_back(std::move(validator)); + } + } + } + + std::unique_ptr> properties; + it = sch.find("properties"); + if (it != sch.object_range().end()) + { + properties = this->make_properties_validator(context, it->value(), sch, anchor_dict); + } + std::unique_ptr> pattern_properties; + + #if defined(JSONCONS_HAS_STD_REGEX) + it = sch.find("patternProperties"); + if (it != sch.object_range().end()) + { + pattern_properties = make_pattern_properties_validator(context, it->value(), sch, anchor_dict); + } + #endif + + it = sch.find("additionalProperties"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_additional_properties_validator(context, it->value(), sch, + std::move(properties), std::move(pattern_properties), anchor_dict)); + } + else + { + if (properties) + { + validators.emplace_back(std::move(properties)); + } +#if defined(JSONCONS_HAS_STD_REGEX) + if (pattern_properties) + { + validators.emplace_back(std::move(pattern_properties)); + } +#endif + } + + it = sch.find("items"); + if (it != sch.object_range().end()) + { + + if (it->value().type() == json_type::array_value) + { + validators.emplace_back(this->make_prefix_items_validator_07(context, it->value(), sch, anchor_dict)); + } + else if (it->value().type() == json_type::object_value || + it->value().type() == json_type::bool_value) + { + validators.emplace_back(this->make_items_validator("items", context, it->value(), sch, anchor_dict)); + } + } + return jsoncons::make_unique>(context.get_base_uri(), + std::move(id), std::move(validators), std::move(defs), std::move(default_value)); + } + +#if defined(JSONCONS_HAS_STD_REGEX) + + std::unique_ptr> make_pattern_properties_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::vector> pattern_properties; + + for (const auto& prop : sch.object_range()) + { + std::string sub_keys[] = {prop.key()}; + pattern_properties.emplace_back( + std::make_pair( + std::regex(prop.key(), std::regex::ECMAScript), + make_schema_validator(context, prop.value(), sub_keys, anchor_dict))); + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(pattern_properties)); + } +#endif + + std::unique_ptr> make_maximum_validator_4(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("maximum"); + if (!sch.is_number()) + { + std::string message("maximum must be a number value"); + JSONCONS_THROW(schema_error(message)); + } + + bool is_exclusive = false; + + if (parent.is_object()) + { + auto it = parent.find("exclusiveMaximum"); + if (it != parent.object_range().end()) + { + is_exclusive = it->value().as_bool(); + } + } + if (is_exclusive) + { + return jsoncons::make_unique>(parent, schema_location, sch); + } + else + { + return jsoncons::make_unique>(parent, schema_location, sch); + } + } + + virtual std::unique_ptr> make_minimum_validator_4(const compilation_context& context, + const Json& sch, const Json& parent) + { + uri schema_location = context.make_schema_location("minimum"); + + if (!sch.is_number()) + { + std::string message("minimum must be an integer"); + JSONCONS_THROW(schema_error(message)); + } + + bool is_exclusive = false; + if (parent.is_object()) + { + auto it = parent.find("exclusiveMinimum"); + if (it != parent.object_range().end()) + { + is_exclusive = it->value().as_bool(); + } + } + if (is_exclusive) + { + return jsoncons::make_unique>(parent, schema_location, sch); + } + else + { + return jsoncons::make_unique>(parent, schema_location, sch); + } + } + + private: + + compilation_context make_compilation_context(const compilation_context& parent, + const Json& sch, jsoncons::span keys) const override + { + // Exclude uri's that are not plain name identifiers + std::vector new_uris; + for (const auto& uri : parent.uris()) + { + if (!uri.has_plain_name_fragment()) + { + new_uris.push_back(uri); + } + } + + // Append the keys for this sub-schema to the uri's + for (const auto& key : keys) + { + for (auto& uri : new_uris) + { + auto new_u = uri.append(key); + uri = uri_wrapper(new_u); + } + } + jsoncons::optional id; + if (sch.is_object()) + { + auto it = sch.find("id"); // If id is found, this schema can be referenced by the id + if (it != sch.object_range().end()) + { + uri_wrapper relative(it->value().template as()); + uri_wrapper new_uri = relative.resolve(uri_wrapper{ parent.get_base_uri() }); + id = new_uri.uri(); + //std::cout << "id: " << id << ", " << new_uri.string() << "\n"; + // Add it to the list if it is not already there + if (std::find(new_uris.begin(), new_uris.end(), new_uri) == new_uris.end()) + { + new_uris.emplace_back(new_uri); + } + } + } + +/* + std::cout << "Absolute URI: " << parent.get_base_uri().string() << "\n"; + for (const auto& uri : new_uris) + { + std::cout << " " << uri.string() << "\n"; + } +*/ + return compilation_context(new_uris, id); + } + private: + static const std::unordered_set& known_keywords() + { + static std::unordered_set keywords{ + "id", + "$ref", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "default", + "definitions", + "dependencies", + "enum", + "exclusiveMaximum", + "exclusiveMaximum", + "exclusiveMinimum", + "exclusiveMinimum", + "items", + "maximum", + "maxItems", + "maxLength", + "maxProperties", + "minimum", + "minItems", + "minLength", + "minProperties", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "properties", + "propertyNames", + "readOnly", + "required", + "type", + "uniqueItems", + "writeOnly" + }; + return keywords; + } + }; + +} // namespace draft4 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_DRAFT4_KEYWORD_FACTORY_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft4/schema_draft4.hpp b/third_party/jsoncons_ext/jsonschema/draft4/schema_draft4.hpp new file mode 100644 index 0000000000..32ffa281a4 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft4/schema_draft4.hpp @@ -0,0 +1,181 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_SCHEMA_DRAFT4_HPP +#define JSONCONS_JSONSCHEMA_SCHEMA_DRAFT4_HPP + +#include + +namespace jsoncons { +namespace jsonschema { +namespace draft4 { + + template + struct schema_draft4 + { + static Json get_schema() + { + static Json sch = Json::parse(R"( +{ + "id": "http://json-schema.org/draft-04/schema#", + "$schema": "http://json-schema.org/draft-04/schema#", + "description": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "positiveInteger": { + "type": "integer", + "minimum": 0 + }, + "positiveIntegerDefault0": { + "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ] + }, + "simpleTypes": { + "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1, + "uniqueItems": true + } + }, + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "$schema": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "multipleOf": { + "type": "number", + "minimum": 0, + "exclusiveMinimum": true + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "boolean", + "default": false + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "boolean", + "default": false + }, + "maxLength": { "$ref": "#/definitions/positiveInteger" }, + "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/positiveInteger" }, + "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "maxProperties": { "$ref": "#/definitions/positiveInteger" }, + "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { + "anyOf": [ + { "type": "boolean" }, + { "$ref": "#" } + ], + "default": {} + }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "enum": { + "type": "array", + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "dependencies": { + "exclusiveMaximum": [ "maximum" ], + "exclusiveMinimum": [ "minimum" ] + }, + "default": {} +} + )"); + + return sch; + } + }; + +} // namespace draft4 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_DRAFT4_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft6/schema_builder_6.hpp b/third_party/jsoncons_ext/jsonschema/draft6/schema_builder_6.hpp new file mode 100644 index 0000000000..258625f114 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft6/schema_builder_6.hpp @@ -0,0 +1,413 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_DRAFT6_SCHEMA_BUILDER_6_HPP +#define JSONCONS_JSONSCHEMA_DRAFT6_SCHEMA_BUILDER_6_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif + +namespace jsoncons { +namespace jsonschema { +namespace draft6 { + + template + class schema_builder_6 : public schema_builder + { + public: + using schema_store_type = typename schema_builder::schema_store_type; + using schema_builder_factory_type = typename schema_builder::schema_builder_factory_type; + using keyword_validator_type = typename std::unique_ptr>; + using schema_validator_type = typename std::unique_ptr>; + using anchor_uri_map_type = std::unordered_map; + private: + + using keyword_factory_type = std::function; + + std::unordered_map keyword_factory_map_; + + public: + schema_builder_6(Json&& sch, const schema_builder_factory_type& builder_factory, + evaluation_options options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers) + : schema_builder(schema_version::draft6(), std::move(sch), builder_factory, options, schema_store_ptr, resolvers) + { + init(); + } + + schema_builder_6(const schema_builder_6&) = delete; + schema_builder_6& operator=(const schema_builder_6&) = delete; + schema_builder_6(schema_builder_6&&) = default; + schema_builder_6& operator=(schema_builder_6&&) = default; + + void init() + { + keyword_factory_map_.emplace("type", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_type_validator(context, sch, parent);}); + keyword_factory_map_.emplace("contentEncoding", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_encoding_validator(context, sch, parent);}); + keyword_factory_map_.emplace("contentMediaType", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_media_type_validator(context, sch, parent);}); + if (this->options().require_format_validation()) + { + keyword_factory_map_.emplace("format", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_format_validator(context, sch, parent);}); + } +#if defined(JSONCONS_HAS_STD_REGEX) + keyword_factory_map_.emplace("pattern", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_pattern_validator(context, sch, parent);}); +#endif + keyword_factory_map_.emplace("maxItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("maxProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_properties_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_properties_validator(context, sch, parent);}); + keyword_factory_map_.emplace("contains", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + {return this->make_contains_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("uniqueItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_unique_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("maxLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_length_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_length_validator(context, sch, parent);}); + keyword_factory_map_.emplace("not", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_not_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("maximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_maximum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("exclusiveMaximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_maximum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_minimum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("exclusiveMinimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_minimum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("multipleOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_multiple_of_validator(context, sch, parent);}); + keyword_factory_map_.emplace("const", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_const_validator(context, sch, parent);}); + keyword_factory_map_.emplace("enum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_enum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("allOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_all_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("anyOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_any_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("oneOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_one_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("dependencies", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_dependencies_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("propertyNames", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_property_names_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("required", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_required_validator(context, sch, parent);}); + } + + schema_validator_type make_schema_validator( + const compilation_context& context, const Json& sch, jsoncons::span keys, + anchor_uri_map_type& anchor_dict) override + { + auto new_context = make_compilation_context(context, sch, keys); + //std::cout << "make_schema_validator " << context.get_base_uri().string() << ", " << new_context.get_base_uri().string() << "\n\n"; + + schema_validator_type schema_validator_ptr; + + switch (sch.type()) + { + case json_type::bool_value: + { + schema_validator_ptr = this->make_boolean_schema(new_context, sch); + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + } + break; + } + case json_type::object_value: + { + auto it = sch.find("$ref"); + if (it != sch.object_range().end()) // this schema is a reference + { + std::vector validators; + std::map defs; + + auto it2 = sch.find("definitions"); + if (it2 != sch.object_range().end()) + { + for (const auto& def : it2->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + } + + Json default_value{ jsoncons::null_type() }; + uri_wrapper relative(it->value().template as()); + auto id = relative.resolve(uri_wrapper{ context.get_base_uri() }); + validators.push_back(this->get_or_create_reference(sch, id)); + schema_validator_ptr = jsoncons::make_unique>( + new_context.get_base_uri(), context.id(), + std::move(validators), std::move(defs), std::move(default_value)); + } + else + { + schema_validator_ptr = make_object_schema_validator(new_context, sch, anchor_dict); + } + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + for (const auto& item : sch.object_range()) + { + if (known_keywords().find(item.key()) == known_keywords().end()) + { + this->insert_unknown_keyword(uri, item.key(), item.value()); // save unknown keywords for later reference + } + } + } + break; + } + default: + JSONCONS_THROW(schema_error("invalid JSON-type for a schema for " + new_context.get_base_uri().string() + ", expected: boolean or object")); + break; + } + + return schema_validator_ptr; + } + + schema_validator_type make_object_schema_validator(const compilation_context& context, + const Json& sch, anchor_uri_map_type& anchor_dict) + { + jsoncons::optional id = context.id(); + Json default_value{ jsoncons::null_type() }; + std::vector validators; + std::map defs; + + auto it = sch.find("definitions"); + if (it != sch.object_range().end()) + { + for (const auto& def : it->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + } + + it = sch.find("default"); + if (it != sch.object_range().end()) + { + default_value = it->value(); + } + + for (const auto& key_value : sch.object_range()) + { + auto factory_it = keyword_factory_map_.find(key_value.key()); + if (factory_it != keyword_factory_map_.end()) + { + auto validator = factory_it->second(context, key_value.value(), sch, anchor_dict); + if (validator) + { + validators.emplace_back(std::move(validator)); + } + } + } + + std::unique_ptr> properties; + it = sch.find("properties"); + if (it != sch.object_range().end()) + { + properties = this->make_properties_validator(context, it->value(), sch, anchor_dict); + } + std::unique_ptr> pattern_properties; + + #if defined(JSONCONS_HAS_STD_REGEX) + it = sch.find("patternProperties"); + if (it != sch.object_range().end()) + { + pattern_properties = make_pattern_properties_validator(context, it->value(), sch, anchor_dict); + } + #endif + + it = sch.find("additionalProperties"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_additional_properties_validator(context, it->value(), sch, + std::move(properties), std::move(pattern_properties), anchor_dict)); + } + else + { + if (properties) + { + validators.emplace_back(std::move(properties)); + } +#if defined(JSONCONS_HAS_STD_REGEX) + if (pattern_properties) + { + validators.emplace_back(std::move(pattern_properties)); + } +#endif + } + + it = sch.find("items"); + if (it != sch.object_range().end()) + { + + if (it->value().type() == json_type::array_value) + { + validators.emplace_back(this->make_prefix_items_validator_07(context, it->value(), sch, anchor_dict)); + } + else if (it->value().type() == json_type::object_value || + it->value().type() == json_type::bool_value) + { + validators.emplace_back(this->make_items_validator("items", context, it->value(), sch, anchor_dict)); + } + } + return jsoncons::make_unique>(context.get_base_uri(), + std::move(id), + std::move(validators), std::move(defs), std::move(default_value)); + } + +#if defined(JSONCONS_HAS_STD_REGEX) + + std::unique_ptr> make_pattern_properties_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::vector> pattern_properties; + + for (const auto& prop : sch.object_range()) + { + std::string sub_keys[] = {prop.key()}; + pattern_properties.emplace_back( + std::make_pair( + std::regex(prop.key(), std::regex::ECMAScript), + make_schema_validator(context, prop.value(), sub_keys, anchor_dict))); + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(pattern_properties)); + } +#endif + + private: + + compilation_context make_compilation_context(const compilation_context& parent, + const Json& sch, jsoncons::span keys) const override + { + // Exclude uri's that are not plain name identifiers + std::vector new_uris; + for (const auto& uri : parent.uris()) + { + if (!uri.has_plain_name_fragment()) + { + new_uris.push_back(uri); + } + } + + // Append the keys for this sub-schema to the uri's + for (const auto& key : keys) + { + for (auto& uri : new_uris) + { + auto new_u = uri.append(key); + uri = uri_wrapper(new_u); + } + } + jsoncons::optional id; + if (sch.is_object()) + { + auto it = sch.find("$id"); // If $id is found, this schema can be referenced by the id + if (it != sch.object_range().end()) + { + uri_wrapper relative(it->value().template as()); + uri_wrapper new_uri = relative.resolve(uri_wrapper{ parent.get_base_uri() }); + id = new_uri.uri(); + //std::cout << "$id: " << id << ", " << new_uri.string() << "\n"; + // Add it to the list if it is not already there + if (std::find(new_uris.begin(), new_uris.end(), new_uri) == new_uris.end()) + { + new_uris.emplace_back(new_uri); + } + } + } + +/* + std::cout << "Absolute URI: " << parent.get_base_uri().string() << "\n"; + for (const auto& uri : new_uris) + { + std::cout << " " << uri.string() << "\n"; + } +*/ + return compilation_context(new_uris, id); + } + private: + static const std::unordered_set& known_keywords() + { + static std::unordered_set keywords{ + "$id", + "$ref", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "default", + "definitions", + "dependencies", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "items", + "maximum", + "maxItems", + "maxLength", + "maxProperties", + "minimum", + "minItems", + "minLength", + "minProperties", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "properties", + "propertyNames", + "readOnly", + "required", + "type", + "uniqueItems", + "writeOnly" + }; + return keywords; + } + }; + +} // namespace draft6 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_DRAFT7_KEYWORD_FACTORY_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft6/schema_draft6.hpp b/third_party/jsoncons_ext/jsonschema/draft6/schema_draft6.hpp new file mode 100644 index 0000000000..eb0c6e53c8 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft6/schema_draft6.hpp @@ -0,0 +1,185 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_SCHEMA_DRAFT6_HPP +#define JSONCONS_JSONSCHEMA_SCHEMA_DRAFT6_HPP + +#include + +namespace jsoncons { +namespace jsonschema { +namespace draft6 { + + template + struct schema_draft6 + { + static Json get_schema() + { + static Json sch = Json::parse(R"( +{ + "$schema": "http://json-schema.org/draft-06/schema#", + "$id": "http://json-schema.org/draft-06/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": {}, + "examples": { + "type": "array", + "items": {} + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": {} + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": {}, + "enum": { + "type": "array" + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": {} +} + )"); + + return sch; + } + }; + +} // namespace draft6 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft7/schema_builder_7.hpp b/third_party/jsoncons_ext/jsonschema/draft7/schema_builder_7.hpp new file mode 100644 index 0000000000..edb899565e --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft7/schema_builder_7.hpp @@ -0,0 +1,449 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_DRAFT7_SCHEMA_BUILDER_7_HPP +#define JSONCONS_JSONSCHEMA_DRAFT7_SCHEMA_BUILDER_7_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if defined(JSONCONS_HAS_STD_REGEX) +#include +#endif + +namespace jsoncons { +namespace jsonschema { +namespace draft7 { + + template + class schema_builder_7 : public schema_builder + { + public: + using schema_store_type = typename schema_builder::schema_store_type; + using schema_builder_factory_type = typename schema_builder::schema_builder_factory_type; + using keyword_validator_type = typename std::unique_ptr>; + using schema_validator_type = typename std::unique_ptr>; + using anchor_uri_map_type = std::unordered_map; + private: + + using keyword_factory_type = std::function; + + std::unordered_map keyword_factory_map_; + + public: + schema_builder_7(Json&& sch, const schema_builder_factory_type& builder_factory, + evaluation_options options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers) + : schema_builder(schema_version::draft7(), std::move(sch), builder_factory, options, schema_store_ptr, resolvers) + { + init(); + } + + schema_builder_7(const schema_builder_7&) = delete; + schema_builder_7& operator=(const schema_builder_7&) = delete; + schema_builder_7(schema_builder_7&&) = default; + schema_builder_7& operator=(schema_builder_7&&) = default; + + void init() + { + keyword_factory_map_.emplace("type", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_type_validator(context, sch, parent);}); + keyword_factory_map_.emplace("contentEncoding", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&) + { + return this->make_content_encoding_validator(context, sch, parent);} + ); + keyword_factory_map_.emplace("contentMediaType", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_content_media_type_validator(context, sch, parent);}); + if (this->options().require_format_validation()) + { + keyword_factory_map_.emplace("format", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_format_validator(context, sch, parent);}); + } +#if defined(JSONCONS_HAS_STD_REGEX) + keyword_factory_map_.emplace("pattern", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_pattern_validator(context, sch, parent);}); +#endif + keyword_factory_map_.emplace("maxItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("maxProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_properties_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minProperties", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_properties_validator(context, sch, parent);}); + keyword_factory_map_.emplace("contains", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + {return this->make_contains_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("uniqueItems", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_unique_items_validator(context, sch, parent);}); + keyword_factory_map_.emplace("maxLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_max_length_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minLength", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_min_length_validator(context, sch, parent);}); + keyword_factory_map_.emplace("not", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_not_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("maximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_maximum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("exclusiveMaximum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_maximum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("minimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_minimum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("exclusiveMinimum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_exclusive_minimum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("multipleOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_multiple_of_validator(context, sch, parent);}); + keyword_factory_map_.emplace("const", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_const_validator(context, sch, parent);}); + keyword_factory_map_.emplace("enum", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_enum_validator(context, sch, parent);}); + keyword_factory_map_.emplace("allOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_all_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("anyOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_any_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("oneOf", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_one_of_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("dependencies", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_dependencies_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("propertyNames", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict){return this->make_property_names_validator(context, sch, parent, anchor_dict);}); + keyword_factory_map_.emplace("required", + [&](const compilation_context& context, const Json& sch, const Json& parent, anchor_uri_map_type&){return this->make_required_validator(context, sch, parent);}); + } + + schema_validator_type make_schema_validator( + const compilation_context& context, const Json& sch, jsoncons::span keys, + anchor_uri_map_type& anchor_dict) override + { + auto new_context = make_compilation_context(context, sch, keys); + //std::cout << "make_schema_validator " << context.get_base_uri().string() << ", " << new_context.get_base_uri().string() << "\n\n"; + + schema_validator_type schema_validator_ptr; + + switch (sch.type()) + { + case json_type::bool_value: + { + schema_validator_ptr = this->make_boolean_schema(new_context, sch); + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + } + break; + } + case json_type::object_value: + { + auto it = sch.find("$ref"); + if (it != sch.object_range().end()) // this schema is a reference + { + std::vector validators; + std::map defs; + + auto it2 = sch.find("definitions"); + if (it2 != sch.object_range().end()) + { + for (const auto& def : it2->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + } + + Json default_value{ jsoncons::null_type() }; + uri_wrapper relative(it->value().template as()); + auto id = relative.resolve(uri_wrapper{ context.get_base_uri() }); + validators.push_back(this->get_or_create_reference(sch, id)); + schema_validator_ptr = jsoncons::make_unique>( + new_context.get_base_uri(), context.id(), + std::move(validators), std::move(defs), std::move(default_value)); + } + else + { + schema_validator_ptr = make_object_schema_validator(new_context, sch, anchor_dict); + } + schema_validator* p = schema_validator_ptr.get(); + for (const auto& uri : new_context.uris()) + { + this->insert_schema(uri, p); + for (const auto& item : sch.object_range()) + { + if (known_keywords().find(item.key()) == known_keywords().end()) + { + this->insert_unknown_keyword(uri, item.key(), item.value()); // save unknown keywords for later reference + } + } + } + break; + } + default: + JSONCONS_THROW(schema_error("invalid JSON-type for a schema for " + new_context.get_base_uri().string() + ", expected: boolean or object")); + break; + } + + return schema_validator_ptr; + } + + schema_validator_type make_object_schema_validator(const compilation_context& context, + const Json& sch, anchor_uri_map_type& anchor_dict) + { + jsoncons::optional id = context.id(); + Json default_value{ jsoncons::null_type() }; + std::vector validators; + std::map defs; + + auto it = sch.find("definitions"); + if (it != sch.object_range().end()) + { + for (const auto& def : it->value().object_range()) + { + std::string sub_keys[] = { "definitions", def.key() }; + defs.emplace(def.key(), make_schema_validator(context, def.value(), sub_keys, anchor_dict)); + } + } + + it = sch.find("default"); + if (it != sch.object_range().end()) + { + default_value = it->value(); + } + + for (const auto& key_value : sch.object_range()) + { + auto factory_it = keyword_factory_map_.find(key_value.key()); + if (factory_it != keyword_factory_map_.end()) + { + auto validator = factory_it->second(context, key_value.value(), sch, anchor_dict); + if (validator) + { + validators.emplace_back(std::move(validator)); + } + } + } + + schema_validator_type if_validator; + schema_validator_type then_validator; + schema_validator_type else_validator; + + it = sch.find("if"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "if" }; + if_validator = make_schema_validator(context, it->value(), sub_keys, anchor_dict); + } + + it = sch.find("then"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "then" }; + then_validator = make_schema_validator(context, it->value(), sub_keys, anchor_dict); + } + + it = sch.find("else"); + if (it != sch.object_range().end()) + { + std::string sub_keys[] = { "else" }; + else_validator = make_schema_validator(context, it->value(), sub_keys, anchor_dict); + } + if (if_validator || then_validator || else_validator) + { + validators.emplace_back(jsoncons::make_unique>( + sch, context.get_base_uri(), + std::move(if_validator), std::move(then_validator), std::move(else_validator))); + } + + std::unique_ptr> properties; + it = sch.find("properties"); + if (it != sch.object_range().end()) + { + properties = this->make_properties_validator(context, it->value(), sch, anchor_dict); + } + std::unique_ptr> pattern_properties; + + #if defined(JSONCONS_HAS_STD_REGEX) + it = sch.find("patternProperties"); + if (it != sch.object_range().end()) + { + pattern_properties = make_pattern_properties_validator(context, it->value(), sch, anchor_dict); + } + #endif + + it = sch.find("additionalProperties"); + if (it != sch.object_range().end()) + { + validators.emplace_back(this->make_additional_properties_validator(context, it->value(), sch, + std::move(properties), std::move(pattern_properties), anchor_dict)); + } + else + { + if (properties) + { + validators.emplace_back(std::move(properties)); + } +#if defined(JSONCONS_HAS_STD_REGEX) + if (pattern_properties) + { + validators.emplace_back(std::move(pattern_properties)); + } +#endif + } + + it = sch.find("items"); + if (it != sch.object_range().end()) + { + + if (it->value().type() == json_type::array_value) + { + validators.emplace_back(this->make_prefix_items_validator_07(context, it->value(), sch, anchor_dict)); + } + else if (it->value().type() == json_type::object_value || + it->value().type() == json_type::bool_value) + { + validators.emplace_back(this->make_items_validator("items", context, it->value(), sch, anchor_dict)); + } + } + return jsoncons::make_unique>(context.get_base_uri(), std::move(id), + std::move(validators), std::move(defs), std::move(default_value)); + } + +#if defined(JSONCONS_HAS_STD_REGEX) + + std::unique_ptr> make_pattern_properties_validator(const compilation_context& context, + const Json& sch, const Json& parent, anchor_uri_map_type& anchor_dict) + { + uri schema_location = context.get_base_uri(); + std::vector> pattern_properties; + + for (const auto& prop : sch.object_range()) + { + std::string sub_keys[] = {prop.key()}; + pattern_properties.emplace_back( + std::make_pair( + std::regex(prop.key(), std::regex::ECMAScript), + make_schema_validator(context, prop.value(), sub_keys, anchor_dict))); + } + + return jsoncons::make_unique>(parent, std::move(schema_location), + std::move(pattern_properties)); + } +#endif + + private: + + compilation_context make_compilation_context(const compilation_context& parent, + const Json& sch, jsoncons::span keys) const override + { + // Exclude uri's that are not plain name identifiers + std::vector new_uris; + for (const auto& uri : parent.uris()) + { + if (!uri.has_plain_name_fragment()) + { + new_uris.push_back(uri); + } + } + + // Append the keys for this sub-schema to the uri's + for (const auto& key : keys) + { + for (auto& uri : new_uris) + { + auto new_u = uri.append(key); + uri = uri_wrapper(new_u); + } + } + jsoncons::optional id; + if (sch.is_object()) + { + auto it = sch.find("$id"); // If $id is found, this schema can be referenced by the id + if (it != sch.object_range().end()) + { + uri_wrapper relative(it->value().template as()); + uri_wrapper new_uri = relative.resolve(uri_wrapper{ parent.get_base_uri() }); + id = new_uri.uri(); + //std::cout << "$id: " << id << ", " << new_uri.string() << "\n"; + // Add it to the list if it is not already there + if (std::find(new_uris.begin(), new_uris.end(), new_uri) == new_uris.end()) + { + new_uris.emplace_back(new_uri); + } + } + } + +/* + std::cout << "Absolute URI: " << parent.get_base_uri().string() << "\n"; + for (const auto& uri : new_uris) + { + std::cout << " " << uri.string() << "\n"; + } +*/ + return compilation_context(new_uris, id); + } + private: + static const std::unordered_set& known_keywords() + { + static std::unordered_set keywords{ + "$id", + "$ref", + "additionalItems", + "additionalProperties", + "allOf", + "anyOf", + "const", + "contains", + "contentEncoding", + "contentMediaType", + "default", + "definitions", + "dependencies", + "enum", + "exclusiveMaximum", + "exclusiveMinimum", + "if", + "then", + "else", + "items", + "maximum", + "maxItems", + "maxLength", + "maxProperties", + "minimum", + "minItems", + "minLength", + "minProperties", + "multipleOf", + "not", + "oneOf", + "pattern", + "patternProperties", + "properties", + "propertyNames", + "readOnly", + "required", + "type", + "uniqueItems", + "writeOnly" + }; + return keywords; + } + }; + +} // namespace draft7 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_DRAFT7_KEYWORD_FACTORY_HPP diff --git a/third_party/jsoncons_ext/jsonschema/draft7/schema_draft7.hpp b/third_party/jsoncons_ext/jsonschema/draft7/schema_draft7.hpp new file mode 100644 index 0000000000..26aba0127c --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/draft7/schema_draft7.hpp @@ -0,0 +1,200 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP +#define JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP + +#include + +namespace jsoncons { +namespace jsonschema { +namespace draft7 { + + template + struct schema_draft7 + { + static Json get_schema() + { + static Json sch = Json::parse(R"( + { + "$schema": "http://json-schema.org/draft-07/schema#", + "$id": "http://json-schema.org/draft-07/schema#", + "title": "Core schema meta-schema", + "definitions": { + "schemaArray": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#" } + }, + "nonNegativeInteger": { + "type": "integer", + "minimum": 0 + }, + "nonNegativeIntegerDefault0": { + "allOf": [ + { "$ref": "#/definitions/nonNegativeInteger" }, + { "default": 0 } + ] + }, + "simpleTypes": { + "enum": [ + "array", + "boolean", + "integer", + "null", + "number", + "object", + "string" + ] + }, + "stringArray": { + "type": "array", + "items": { "type": "string" }, + "uniqueItems": true, + "default": [] + } + }, + "type": ["object", "boolean"], + "properties": { + "$id": { + "type": "string", + "format": "uri-reference" + }, + "$schema": { + "type": "string", + "format": "uri" + }, + "$ref": { + "type": "string", + "format": "uri-reference" + }, + "$comment": { + "type": "string" + }, + "title": { + "type": "string" + }, + "description": { + "type": "string" + }, + "default": true, + "readOnly": { + "type": "boolean", + "default": false + }, + "examples": { + "type": "array", + "items": true + }, + "multipleOf": { + "type": "number", + "exclusiveMinimum": 0 + }, + "maximum": { + "type": "number" + }, + "exclusiveMaximum": { + "type": "number" + }, + "minimum": { + "type": "number" + }, + "exclusiveMinimum": { + "type": "number" + }, + "maxLength": { "$ref": "#/definitions/nonNegativeInteger" }, + "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "pattern": { + "type": "string", + "format": "regex" + }, + "additionalItems": { "$ref": "#" }, + "items": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/schemaArray" } + ], + "default": true + }, + "maxItems": { "$ref": "#/definitions/nonNegativeInteger" }, + "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "uniqueItems": { + "type": "boolean", + "default": false + }, + "contains": { "$ref": "#" }, + "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" }, + "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" }, + "required": { "$ref": "#/definitions/stringArray" }, + "additionalProperties": { "$ref": "#" }, + "definitions": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "properties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "default": {} + }, + "patternProperties": { + "type": "object", + "additionalProperties": { "$ref": "#" }, + "propertyNames": { "format": "regex" }, + "default": {} + }, + "dependencies": { + "type": "object", + "additionalProperties": { + "anyOf": [ + { "$ref": "#" }, + { "$ref": "#/definitions/stringArray" } + ] + } + }, + "propertyNames": { "$ref": "#" }, + "const": true, + "enum": { + "type": "array", + "items": true, + "minItems": 1, + "uniqueItems": true + }, + "type": { + "anyOf": [ + { "$ref": "#/definitions/simpleTypes" }, + { + "type": "array", + "items": { "$ref": "#/definitions/simpleTypes" }, + "minItems": 1, + "uniqueItems": true + } + ] + }, + "format": { "type": "string" }, + "contentMediaType": { "type": "string" }, + "contentEncoding": { "type": "string" }, + "if": { "$ref": "#" }, + "then": { "$ref": "#" }, + "else": { "$ref": "#" }, + "allOf": { "$ref": "#/definitions/schemaArray" }, + "anyOf": { "$ref": "#/definitions/schemaArray" }, + "oneOf": { "$ref": "#/definitions/schemaArray" }, + "not": { "$ref": "#" } + }, + "default": true + } + )"); + + return sch; + } + }; + +} // namespace draft7 +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_DRAFT7_HPP diff --git a/third_party/jsoncons_ext/jsonschema/evaluation_options.hpp b/third_party/jsoncons_ext/jsonschema/evaluation_options.hpp new file mode 100644 index 0000000000..61e2b8855c --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/evaluation_options.hpp @@ -0,0 +1,112 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_EVALUATION_OPTIONS_HPP +#define JSONCONS_JSONSCHEMA_EVALUATION_OPTIONS_HPP + +#include + +namespace jsoncons { +namespace jsonschema { + + struct schema_version + { + static std::string draft4() + { + static std::string s{"http://json-schema.org/draft-04/schema#"}; + return s; + } + static std::string draft6() + { + static std::string s{"http://json-schema.org/draft-06/schema#"}; + return s; + } + static std::string draft7() + { + static std::string s{"http://json-schema.org/draft-07/schema#"}; + return s; + } + static std::string draft201909() + { + static std::string s{"https://json-schema.org/draft/2019-09/schema"}; + return s; + } + static std::string draft202012() + { + static std::string s{"https://json-schema.org/draft/2020-12/schema"}; + return s; + } + }; + + class evaluation_options + { + std::string default_version_; + bool require_format_validation_; + bool compatibility_mode_; + public: + evaluation_options() + : default_version_{schema_version::draft202012()}, + require_format_validation_(false), compatibility_mode_(false) + { + } + + evaluation_options(const evaluation_options& other) + : default_version_(other.default_version_), + require_format_validation_(other.require_format_validation_), + compatibility_mode_(other.compatibility_mode_) + { + } + + evaluation_options& operator=(const evaluation_options& other) + { + default_version_ = other.default_version_; + require_format_validation_ = other.require_format_validation_; + compatibility_mode_ = other.compatibility_mode_; + return *this; + } + + bool require_format_validation() const + { + return require_format_validation_; + } + evaluation_options& require_format_validation(bool value) + { + require_format_validation_ = value; + return *this; + } + + bool compatibility_mode() const + { + return compatibility_mode_; + } + evaluation_options& compatibility_mode(bool value) + { + compatibility_mode_ = value; + return *this; + } + + const std::string& default_version() const + { + return default_version_; + } + evaluation_options& default_version(const std::string& version) + { + default_version_ = version; + return *this; + } + + friend bool operator==(const evaluation_options& lhs, const evaluation_options& rhs) + { + return lhs.default_version_ == rhs.default_version_ + && lhs.require_format_validation_ == rhs.require_format_validation_ + && lhs.compatibility_mode_ == rhs.compatibility_mode_; + } + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_COMMON_SCHEMA_HPP diff --git a/third_party/jsoncons_ext/jsonschema/json_schema.hpp b/third_party/jsoncons_ext/jsonschema/json_schema.hpp new file mode 100644 index 0000000000..e4c10b1762 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/json_schema.hpp @@ -0,0 +1,233 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_JSON_SCHEMA_HPP +#define JSONCONS_JSONSCHEMA_JSON_SCHEMA_HPP + +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + class validation_message_to_json_events + { + json_visitor* visitor_ptr_; + public: + validation_message_to_json_events(json_visitor& visitor) + : visitor_ptr_(std::addressof(visitor)) + { + } + + walk_result operator()(const validation_message& message) + { + write_error(message); + return walk_result::advance; + } + + void write_error(const validation_message& message) + { + visitor_ptr_->begin_object(); + + visitor_ptr_->key("valid"); + visitor_ptr_->bool_value(false); + + visitor_ptr_->key("evaluationPath"); + visitor_ptr_->string_value(message.eval_path().string()); + + visitor_ptr_->key("schemaLocation"); + visitor_ptr_->string_value(message.schema_location().string()); + + visitor_ptr_->key("instanceLocation"); + visitor_ptr_->string_value(message.instance_location().string()); + + visitor_ptr_->key("error"); + visitor_ptr_->string_value(message.message()); + + if (!message.details().empty()) + { + visitor_ptr_->key("details"); + visitor_ptr_->begin_array(); + for (const auto& detail : message.details()) + { + write_error(detail); + } + visitor_ptr_->end_array(); + } + + visitor_ptr_->end_object(); + } + }; + + class throwing_error_listener : public error_reporter + { + walk_result do_error(const validation_message& msg) override + { + JSONCONS_THROW(validation_error(msg.instance_location().string() + ": " + msg.message())); + } + }; + + class fail_early_reporter : public error_reporter + { + walk_result do_error(const validation_message&) override + { + return walk_result::abort; + } + }; + + using error_reporter_t = std::function; + + struct error_reporter_adaptor : public error_reporter + { + error_reporter_t reporter_; + + error_reporter_adaptor(const error_reporter_t& reporter) + : reporter_(reporter) + { + } + private: + walk_result do_error(const validation_message& e) override + { + return reporter_(e); + } + }; + + template + class json_validator; + + template + class json_schema + { + using keyword_validator_type = std::unique_ptr>; + using document_schema_validator_type = std::unique_ptr>; + + document_schema_validator_type root_; + + friend class json_validator; + public: + json_schema(document_schema_validator_type&& root) + : root_(std::move(root)) + { + if (root_ == nullptr) + JSONCONS_THROW(schema_error("There is no root schema to validate an instance against")); + } + + json_schema(const json_schema&) = delete; + json_schema(json_schema&&) = default; + json_schema& operator=(const json_schema&) = delete; + json_schema& operator=(json_schema&&) = default; + + // Validate input JSON against a JSON Schema with a default throwing error reporter + Json validate(const Json& instance) const + { + throwing_error_listener reporter; + jsonpointer::json_pointer instance_location{}; + Json patch(json_array_arg); + + evaluation_context context; + evaluation_results results; + root_->validate(context, instance, instance_location, results, reporter, patch); + return patch; + } + + // Validate input JSON against a JSON Schema + bool is_valid(const Json& instance) const + { + fail_early_reporter reporter; + jsonpointer::json_pointer instance_location{}; + Json patch(json_array_arg); + + evaluation_context context; + evaluation_results results; + root_->validate(context, instance, instance_location, results, reporter, patch); + return reporter.error_count() == 0; + } + + // Validate input JSON against a JSON Schema with a provided error reporter + template + typename std::enable_if::value,void>::type + validate(const Json& instance, const MsgReporter& reporter) const + { + jsonpointer::json_pointer instance_location{}; + Json patch(json_array_arg); + + error_reporter_adaptor adaptor(reporter); + evaluation_context context; + evaluation_results results; + root_->validate(context, instance, instance_location, results, adaptor, patch); + } + + // Validate input JSON against a JSON Schema with a provided error reporter + template + typename std::enable_if::value,void>::type + validate(const Json& instance, MsgReporter&& reporter, Json& patch) const + { + jsonpointer::json_pointer instance_location{}; + patch = Json(json_array_arg); + + error_reporter_adaptor adaptor(std::forward(reporter)); + evaluation_context context; + evaluation_results results; + root_->validate(context, instance, instance_location, results, adaptor, patch); + } + + // Validate input JSON against a JSON Schema with a provided error reporter + void validate(const Json& instance, Json& patch) const + { + jsonpointer::json_pointer instance_location{}; + patch = Json(json_array_arg); + + fail_early_reporter reporter; + evaluation_context context; + evaluation_results results; + root_->validate(context, instance, instance_location, results, reporter, patch); + } + + // Validate input JSON against a JSON Schema with a provided json_visitor + void validate(const Json& instance, json_visitor& visitor) const + { + visitor.begin_array(); + jsonpointer::json_pointer instance_location{}; + Json patch{json_array_arg}; + + validation_message_to_json_events adaptor{ visitor }; + evaluation_context context; + evaluation_results results; + error_reporter_adaptor reporter(adaptor); + root_->validate(context, instance, instance_location, results, reporter, patch); + visitor.end_array(); + visitor.flush(); + } + + template + void walk(const Json& instance, const WalkReporter& reporter) const + { + jsonpointer::json_pointer instance_location{}; + + root_->walk(evaluation_context{}, instance, instance_location, reporter); + } + + private: + // Validate input JSON against a JSON Schema with a provided error reporter + void validate2(const Json& instance, error_reporter& reporter, Json& patch) const + { + jsonpointer::json_pointer instance_location{}; + patch = Json(json_array_arg); + + evaluation_context context; + evaluation_results results; + root_->validate(context, instance, instance_location, results, reporter, patch); + } + }; + + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_HPP diff --git a/third_party/jsoncons_ext/jsonschema/json_schema_factory.hpp b/third_party/jsoncons_ext/jsonschema/json_schema_factory.hpp new file mode 100644 index 0000000000..5ad0e6021b --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/json_schema_factory.hpp @@ -0,0 +1,352 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_JSON_SCHEMA_FACTORY_HPP +#define JSONCONS_JSONSCHEMA_JSON_SCHEMA_FACTORY_HPP + +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + template + class schema_builder_factory + { + public: + using schema_store_type = std::map*>; + + schema_builder_factory() + { + } + + std::unique_ptr> operator()(Json sch, + const evaluation_options& options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers, + const std::unordered_map& vocabulary) const + { + std::unique_ptr> builder; + + if (sch.is_object()) + { + auto it = sch.find("$schema"); + if (it != sch.object_range().end()) + { + builder = get_builder(std::move(sch), it->value().as_string_view(), options, schema_store_ptr, resolvers, vocabulary); + if (!builder) + { + std::string message("Unsupported schema version "); + message.append(it->value().template as()); + JSONCONS_THROW(schema_error(message)); + } + } + else + { + builder = get_default_schema_builder(std::move(sch), options, schema_store_ptr, resolvers, vocabulary); + } + } + else if (sch.is_bool()) + { + builder = get_default_schema_builder(std::move(sch), options, schema_store_ptr, resolvers, vocabulary); + } + else + { + JSONCONS_THROW(schema_error("Schema must be object or boolean")); + } + return builder; + } + + std::unique_ptr> get_default_schema_builder(Json&& sch, + const evaluation_options& options, + schema_store_type* schema_store_ptr, + const std::vector>& resolvers, + const std::unordered_map& vocabulary) const + { + if (options.default_version() == schema_version::draft202012()) + { + return jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers, vocabulary); + } + else if (options.default_version() == schema_version::draft201909()) + { + return jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers, vocabulary); + } + else if (options.default_version() == schema_version::draft7()) + { + return jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers); + } + else if (options.default_version() == schema_version::draft6()) + { + return jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers); + } + else if (options.default_version() == schema_version::draft4()) + { + return jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers); + } + else + { + JSONCONS_THROW(schema_error("Unsupported schema version " + options.default_version())); + } + } + + std::unique_ptr> get_builder(Json&& sch, const jsoncons::string_view& schema_id, + const evaluation_options& options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers, + const std::unordered_map& vocabulary) const + { + std::unique_ptr> builder; + + if (schema_id == schema_version::draft202012()) + { + builder = jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers, vocabulary); + } + else if (schema_id == schema_version::draft201909()) + { + builder = jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers, vocabulary); + } + else if (schema_id == schema_version::draft7()) + { + builder = jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers); + } + else if (schema_id == schema_version::draft6()) + { + builder = jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers); + } + else if (schema_id == schema_version::draft4()) + { + builder = jsoncons::make_unique>(std::move(sch), *this, + options, schema_store_ptr, resolvers); + } + else + { + builder = get_builder_from_meta_schema(std::move(sch), schema_id, options, schema_store_ptr, resolvers); + } + return builder; + } + + std::unique_ptr> get_builder_from_meta_schema(Json&& sch, const jsoncons::string_view& schema_id, + const evaluation_options& options, schema_store_type* schema_store_ptr, + const std::vector>& resolvers) const + { + std::unique_ptr> builder; + + bool found = false; + jsoncons::uri uri{ std::string(schema_id) }; + for (auto it = resolvers.begin(); it != resolvers.end() && !found; ++it) + { + Json meta_sch = (*it)(uri.base()); + if (meta_sch.is_object()) + { + std::unordered_map vocabulary; + auto vocab_it = meta_sch.find("$vocabulary"); + if (vocab_it != meta_sch.object_range().end()) + { + const auto& vocab = vocab_it->value(); + if (vocab.is_object()) + { + for (const auto& member : vocab.object_range()) + { + vocabulary.emplace(member.key(), member.value().as_bool()); + } + } + } + auto schema_it = meta_sch.find("$schema"); + if (schema_it != meta_sch.object_range().end()) + { + builder = get_builder(std::move(sch), schema_it->value().as_string_view(), options, schema_store_ptr, resolvers, vocabulary); + found = true; + } + } + } + + return builder; + } + }; + + template + Json meta_resolver(const jsoncons::uri& uri) + { + if (uri.base() == schema_version::draft202012()) + { + return jsoncons::jsonschema::draft202012::schema_draft202012::get_schema(); + } + else if (uri.base() == schema_version::draft201909()) + { + return jsoncons::jsonschema::draft201909::schema_draft201909::get_schema(); + } + else if (uri.base() == schema_version::draft7()) + { + return jsoncons::jsonschema::draft7::schema_draft7::get_schema(); + } + else if (uri.base() == schema_version::draft6()) + { + return jsoncons::jsonschema::draft6::schema_draft6::get_schema(); + } + else if (uri.base() == schema_version::draft4()) + { + return jsoncons::jsonschema::draft4::schema_draft4::get_schema(); + } + else + { + return Json::null(); + } + } + + template + typename std::enable_if::value,json_schema>::type + make_json_schema(Json sch, const std::string& retrieval_uri, const URIResolver& resolver, + evaluation_options options = evaluation_options{}) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver, resolver}}; + auto schema_builder = builder_factory(std::move(sch), options, &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(retrieval_uri); + return json_schema(schema_builder->get_schema_validator()); + } + + template + json_schema make_json_schema(Json sch, const std::string& retrieval_uri, + evaluation_options options = evaluation_options{}) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver}}; + auto schema_builder = builder_factory(std::move(sch), options, &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(retrieval_uri); + return json_schema(schema_builder->get_schema_validator()); + } + + template + typename std::enable_if::value,json_schema>::type + make_json_schema(Json sch, const URIResolver& resolver, + evaluation_options options = evaluation_options{}) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver, resolver}}; + auto schema_builder = builder_factory(std::move(sch), options, &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(); + return json_schema(schema_builder->get_schema_validator()); + } + + template + json_schema make_json_schema(Json sch, + evaluation_options options = evaluation_options{}) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver}}; + auto schema_builder = builder_factory(std::move(sch), options, &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(); + return json_schema(schema_builder->get_schema_validator()); + } + +#if !defined(JSONCONS_NO_DEPRECATED) + + // Legacy + template + typename std::enable_if::value,std::shared_ptr>>::type + make_schema(Json sch, const std::string& retrieval_uri, const URIResolver& resolver) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver, resolver}}; + auto schema_builder = builder_factory(std::move(sch), + jsonschema::evaluation_options{}.default_version(jsonschema::schema_version::draft7()), + &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(retrieval_uri); + return std::make_shared>(schema_builder->get_schema_validator()); + } + + template + std::shared_ptr> make_schema(Json sch, const std::string& retrieval_uri) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver}}; + auto schema_builder = builder_factory(std::move(sch), + jsonschema::evaluation_options{}.default_version(jsonschema::schema_version::draft7()), + &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(retrieval_uri); + return std::make_shared>(schema_builder->get_schema_validator()); + } + + template + typename std::enable_if::value,std::shared_ptr>>::type + make_schema(Json sch, const URIResolver& resolver) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver, resolver}}; + auto schema_builder = builder_factory(std::move(sch), + jsonschema::evaluation_options{}.default_version(jsonschema::schema_version::draft7()), + &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(); + return std::make_shared>(schema_builder->get_schema_validator()); + } + + template + std::shared_ptr> make_schema(Json sch) + { + using schema_store_type = std::map*>; + schema_store_type schema_store; + schema_builder_factory builder_factory{}; + + std::unordered_map vocabulary{}; + std::vector> resolvers = {{meta_resolver}}; + auto schema_builder = builder_factory(std::move(sch), + jsonschema::evaluation_options{}.default_version(jsonschema::schema_version::draft7()), + &schema_store, resolvers, vocabulary); + + schema_builder->build_schema(); + return std::make_shared>(schema_builder->get_schema_validator()); + } +#endif + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_SCHEMA_HPP diff --git a/third_party/jsoncons_ext/jsonschema/json_validator.hpp b/third_party/jsoncons_ext/jsonschema/json_validator.hpp new file mode 100644 index 0000000000..f9a902ea9f --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/json_validator.hpp @@ -0,0 +1,176 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_JSON_VALIDATOR_HPP +#define JSONCONS_JSONSCHEMA_JSON_VALIDATOR_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + +#if !defined(JSONCONS_NO_DEPRECATED) + +class validation_output + { + std::string keyword_; + std::string schema_path_; + std::string instance_location_; + std::string message_; + std::vector nested_errors_; + public: + validation_output(std::string keyword, + std::string schema_path, + std::string instance_location, + std::string message) + : keyword_(std::move(keyword)), + schema_path_(std::move(schema_path)), + instance_location_(std::move(instance_location)), + message_(std::move(message)) + { + } + + validation_output(const std::string& keyword, + const std::string& schema_path, + const std::string& instance_location, + const std::string& message, + const std::vector& nested_errors) + : keyword_(keyword), + schema_path_(schema_path), + instance_location_(instance_location), + message_(message), + nested_errors_(nested_errors) + { + } + + const std::string& instance_location() const + { + return instance_location_; + } + + const std::string& message() const + { + return message_; + } + + const std::string& schema_path() const + { + return schema_path_; + } + + const std::string& keyword() const + { + return keyword_; + } + + const std::vector& nested_errors() const + { + return nested_errors_; + } + }; + + struct validation_message_to_validation_output : public error_reporter + { + using validation_output_reporter_t = std::function; + + validation_output_reporter_t reporter_; + + validation_message_to_validation_output(const validation_output_reporter_t& reporter) + : reporter_(reporter) + { + } + private: + walk_result do_error(const validation_message& m) override + { + std::vector nested_errors; + for (const auto& detail : m.details()) + { + nested_errors.emplace_back(validation_output(detail.keyword(), + detail.schema_location().string(), + detail.instance_location().string(), + detail.message())); + } + + reporter_(validation_output(m.keyword(), + m.schema_location().string(), + m.instance_location().string(), + m.message(), + std::move(nested_errors))); + + return walk_result::advance; + } + }; + + template + class json_validator + { + std::shared_ptr> root_; + + public: + json_validator(std::shared_ptr> root) + : root_(root) + { + } + + json_validator(json_validator &&) = default; + json_validator &operator=(json_validator &&) = default; + + json_validator(json_validator const &) = delete; + json_validator &operator=(json_validator const &) = delete; + + ~json_validator() = default; + + // Validate input JSON against a JSON Schema with a default throwing error reporter + Json validate(const Json& instance) const + { + throwing_error_listener reporter; + Json patch(json_array_arg); + + root_->validate2(instance, reporter, patch); + return patch; + } + + // Validate input JSON against a JSON Schema + bool is_valid(const Json& instance) const + { + fail_early_reporter reporter; + Json patch(json_array_arg); + + root_->validate2(instance, reporter, patch); + return reporter.error_count() == 0; + } + + // Validate input JSON against a JSON Schema with a provided error reporter + template + typename std::enable_if::value,Json>::type + validate(const Json& instance, MsgReporter&& reporter) const + { + Json patch(json_array_arg); + + validation_message_to_validation_output adaptor(reporter); + + root_->validate2(instance, adaptor, patch); + return patch; + } + }; + +#endif + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_JSON_VALIDATOR_HPP diff --git a/third_party/jsoncons_ext/jsonschema/jsonschema.hpp b/third_party/jsoncons_ext/jsonschema/jsonschema.hpp new file mode 100644 index 0000000000..e99fdcaa30 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/jsonschema.hpp @@ -0,0 +1,13 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_JSONSCHEMA_HPP +#define JSONCONS_JSONSCHEMA_JSONSCHEMA_HPP + +#include +#include + +#endif // JSONCONS_JSONSCHEMA_JSONSCHEMA_HPP diff --git a/third_party/jsoncons_ext/jsonschema/jsonschema_error.hpp b/third_party/jsoncons_ext/jsonschema/jsonschema_error.hpp new file mode 100644 index 0000000000..f103e119be --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/jsonschema_error.hpp @@ -0,0 +1,47 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_JSONSCHEMA_ERROR_HPP +#define JSONCONS_JSONSCHEMA_JSONSCHEMA_ERROR_HPP + +#include +#include + +namespace jsoncons { +namespace jsonschema { + + class schema_error : public std::runtime_error, public virtual json_exception + { + public: + schema_error(const std::string& message) + : std::runtime_error(message) + { + } + + const char* what() const noexcept override + { + return std::runtime_error::what(); + } + }; + + class validation_error : public std::runtime_error, public virtual json_exception + { + public: + validation_error(const std::string& message) + : std::runtime_error(message) + { + } + + const char* what() const noexcept override + { + return std::runtime_error::what(); + } + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_JSONSCHEMA_ERROR_HPP diff --git a/third_party/jsoncons_ext/jsonschema/validation_message.hpp b/third_party/jsoncons_ext/jsonschema/validation_message.hpp new file mode 100644 index 0000000000..dbdb1ccad4 --- /dev/null +++ b/third_party/jsoncons_ext/jsonschema/validation_message.hpp @@ -0,0 +1,96 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONSCHEMA_VALIDATION_MESSAGE_HPP +#define JSONCONS_JSONSCHEMA_VALIDATION_MESSAGE_HPP + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace jsonschema { + + class validation_message + { + std::string keyword_; + jsonpointer::json_pointer eval_path_; + uri schema_location_; + jsonpointer::json_pointer instance_location_; + std::string message_; + std::vector details_; + public: + validation_message(std::string keyword, + jsonpointer::json_pointer eval_path, + uri schema_location, + jsonpointer::json_pointer instance_location, + std::string message) + : keyword_(std::move(keyword)), + eval_path_(std::move(eval_path)), + schema_location_(std::move(schema_location)), + instance_location_(std::move(instance_location)), + message_(std::move(message)) + { + } + + validation_message(const std::string& keyword, + const jsonpointer::json_pointer& eval_path, + const uri& schema_location, + const jsonpointer::json_pointer& instance_location, + const std::string& message, + const std::vector& details) + : keyword_(keyword), + eval_path_(eval_path), + schema_location_(schema_location), + instance_location_(instance_location), + message_(message), + details_(details) + { + } + + const jsonpointer::json_pointer& instance_location() const + { + return instance_location_; + } + + const std::string& message() const + { + return message_; + } + + const jsonpointer::json_pointer& eval_path() const + { + return eval_path_; + } + + const uri& schema_location() const + { + return schema_location_; + } + + const std::string& keyword() const + { + return keyword_; + } + + const std::vector& details() const + { + return details_; + } + }; + +} // namespace jsonschema +} // namespace jsoncons + +#endif // JSONCONS_JSONSCHEMA_JSON_VALIDATOR_HPP diff --git a/third_party/jsoncons_ext/mergepatch/mergepatch.hpp b/third_party/jsoncons_ext/mergepatch/mergepatch.hpp new file mode 100644 index 0000000000..6a5647fba8 --- /dev/null +++ b/third_party/jsoncons_ext/mergepatch/mergepatch.hpp @@ -0,0 +1,103 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_JSONMERGEPATCH_JSONMERGEPATCH_HPP +#define JSONCONS_JSONMERGEPATCH_JSONMERGEPATCH_HPP + +#include +#include +#include +#include // std::min +#include // std::move +#include + +namespace jsoncons { +namespace mergepatch { + + template + Json from_diff(const Json& source, const Json& target) + { + if (!source.is_object() || !target.is_object()) + { + return target; + } + Json result(json_object_arg); + + for (const auto& member : source.object_range()) + { + auto it = target.find(member.key()); + if (it != target.object_range().end()) + { + if (member.value() != it->value()) + { + result.try_emplace(member.key(), from_diff(member.value(), it->value())); + } + } + else + { + result.try_emplace(member.key(), Json::null()); + } + } + + for (const auto& member : target.object_range()) + { + auto it = source.find(member.key()); + if (it == source.object_range().end()) + { + result.try_emplace(member.key(), member.value()); + } + } + + return result; + } + + namespace detail { + template + Json apply_merge_patch_(Json& target, const Json& patch) + { + if (patch.is_object()) + { + if (!target.is_object()) + { + target = Json(json_object_arg); + } + for (auto& member : patch.object_range()) + { + auto it = target.find(member.key()); + if (it != target.object_range().end()) + { + Json item = it->value(); + target.erase(it); + if (!member.value().is_null()) + { + target.try_emplace(member.key(), apply_merge_patch_(item, member.value())); + } + } + else if (!member.value().is_null()) + { + Json item(json_object_arg); + target.try_emplace(member.key(), apply_merge_patch_(item, member.value())); + } + } + return target; + } + else + { + return patch; + } + } + } // namespace detail + + template + void apply_merge_patch(Json& target, const Json& patch) + { + target = detail::apply_merge_patch_(target, patch); + } + +} // namespace mergepatch +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/msgpack/decode_msgpack.hpp b/third_party/jsoncons_ext/msgpack/decode_msgpack.hpp new file mode 100644 index 0000000000..187d6f0ce6 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/decode_msgpack.hpp @@ -0,0 +1,203 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_DECODE_MSGPACK_HPP +#define JSONCONS_MSGPACK_DECODE_MSGPACK_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace msgpack { + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_msgpack(const Source& v, + const msgpack_decode_options& options = msgpack_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_msgpack_reader reader(v, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_msgpack(const Source& v, + const msgpack_decode_options& options = msgpack_decode_options()) + { + basic_msgpack_cursor cursor(v, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_msgpack(std::istream& is, + const msgpack_decode_options& options = msgpack_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + msgpack_stream_reader reader(is, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_msgpack(std::istream& is, + const msgpack_decode_options& options = msgpack_decode_options()) + { + basic_msgpack_cursor cursor(is, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_msgpack(InputIt first, InputIt last, + const msgpack_decode_options& options = msgpack_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_msgpack_reader> reader(binary_iterator_source(first, last), adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_msgpack(InputIt first, InputIt last, + const msgpack_decode_options& options = msgpack_decode_options()) + { + basic_msgpack_cursor> cursor(binary_iterator_source(first, last), options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + // With leading allocator_set parameter + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_msgpack(const allocator_set& alloc_set, + const Source& v, + const msgpack_decode_options& options = msgpack_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_msgpack_reader reader(v, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_msgpack(const allocator_set& alloc_set, + const Source& v, + const msgpack_decode_options& options = msgpack_decode_options()) + { + basic_msgpack_cursor cursor(v, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_msgpack(const allocator_set& alloc_set, + std::istream& is, + const msgpack_decode_options& options = msgpack_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_msgpack_reader reader(is, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_msgpack(const allocator_set& alloc_set, + std::istream& is, + const msgpack_decode_options& options = msgpack_decode_options()) + { + basic_msgpack_cursor cursor(is, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + +} // msgpack +} // jsoncons + +#endif diff --git a/third_party/jsoncons_ext/msgpack/encode_msgpack.hpp b/third_party/jsoncons_ext/msgpack/encode_msgpack.hpp new file mode 100644 index 0000000000..7876e3de08 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/encode_msgpack.hpp @@ -0,0 +1,142 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_ENCODE_MSGPACK_HPP +#define JSONCONS_MSGPACK_ENCODE_MSGPACK_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include + +namespace jsoncons { +namespace msgpack { + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_msgpack(const T& j, + ByteContainer& cont, + const msgpack_encode_options& options = msgpack_encode_options()) + { + using char_type = typename T::char_type; + basic_msgpack_encoder> encoder(cont, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_msgpack(const T& val, + ByteContainer& cont, + const msgpack_encode_options& options = msgpack_encode_options()) + { + basic_msgpack_encoder> encoder(cont, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_msgpack(const T& j, + std::ostream& os, + const msgpack_encode_options& options = msgpack_encode_options()) + { + using char_type = typename T::char_type; + msgpack_stream_encoder encoder(os, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_msgpack(const T& val, + std::ostream& os, + const msgpack_encode_options& options = msgpack_encode_options()) + { + msgpack_stream_encoder encoder(os, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // with temp_allocator_arg_t + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_msgpack(const allocator_set& alloc_set, const T& j, + ByteContainer& cont, + const msgpack_encode_options& options = msgpack_encode_options()) + { + using char_type = typename T::char_type; + basic_msgpack_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_msgpack(const allocator_set& alloc_set, + const T& val, ByteContainer& cont, + const msgpack_encode_options& options = msgpack_encode_options()) + { + basic_msgpack_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_msgpack(const allocator_set& alloc_set, + const T& j, + std::ostream& os, + const msgpack_encode_options& options = msgpack_encode_options()) + { + using char_type = typename T::char_type; + basic_msgpack_encoder encoder(os, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_msgpack(const allocator_set& alloc_set, + const T& val, + std::ostream& os, + const msgpack_encode_options& options = msgpack_encode_options()) + { + basic_msgpack_encoder encoder(os, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + +} // msgpack +} // jsoncons + +#endif diff --git a/third_party/jsoncons_ext/msgpack/msgpack.hpp b/third_party/jsoncons_ext/msgpack/msgpack.hpp new file mode 100644 index 0000000000..aeca750723 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack.hpp @@ -0,0 +1,24 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_HPP +#define JSONCONS_MSGPACK_MSGPACK_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +#endif + diff --git a/third_party/jsoncons_ext/msgpack/msgpack_cursor.hpp b/third_party/jsoncons_ext/msgpack/msgpack_cursor.hpp new file mode 100644 index 0000000000..e43308f707 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_cursor.hpp @@ -0,0 +1,283 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_CURSOR_HPP +#define JSONCONS_MSGPACK_MSGPACK_CURSOR_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace msgpack { + +template > +class basic_msgpack_cursor : public basic_staj_cursor, private virtual ser_context +{ +public: + using source_type = Source; + using char_type = char; + using allocator_type = Allocator; +private: + basic_msgpack_parser parser_; + basic_staj_visitor cursor_visitor_; + basic_item_event_visitor_to_json_visitor cursor_handler_adaptor_; + bool eof_; + + // Noncopyable and nonmoveable + basic_msgpack_cursor(const basic_msgpack_cursor&) = delete; + basic_msgpack_cursor& operator=(const basic_msgpack_cursor&) = delete; + +public: + using string_view_type = string_view; + + template + basic_msgpack_cursor(Sourceable&& source, + const msgpack_decode_options& options = msgpack_decode_options(), + const Allocator& alloc = Allocator()) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + cursor_handler_adaptor_(cursor_visitor_, alloc), + eof_(false) + { + if (!done()) + { + next(); + } + } + + // Constructors that set parse error codes + + template + basic_msgpack_cursor(Sourceable&& source, + std::error_code& ec) + : basic_msgpack_cursor(std::allocator_arg, Allocator(), + std::forward(source), + msgpack_decode_options(), + ec) + { + } + + template + basic_msgpack_cursor(Sourceable&& source, + const msgpack_decode_options& options, + std::error_code& ec) + : basic_msgpack_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + ec) + { + } + + template + basic_msgpack_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const msgpack_decode_options& options, + std::error_code& ec) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + cursor_handler_adaptor_(cursor_visitor_, alloc), + eof_(false) + { + if (!done()) + { + next(ec); + } + } + + void reset() + { + parser_.reset(); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + template + void reset(Sourceable&& source) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + void reset(std::error_code& ec) + { + parser_.reset(); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + template + void reset(Sourceable&& source, std::error_code& ec) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + cursor_handler_adaptor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + bool done() const override + { + return parser_.done(); + } + + const staj_event& current() const override + { + return cursor_visitor_.event(); + } + + void read_to(basic_json_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_json_visitor& visitor, + std::error_code& ec) override + { + if (cursor_visitor_.dump(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + const ser_context& context() const override + { + return *this; + } + + bool eof() const + { + return eof_; + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + staj_filter_view operator|(basic_msgpack_cursor& cursor, + std::function pred) + { + return staj_filter_view(cursor, pred); + } +private: + static bool accept_all(const staj_event&, const ser_context&) + { + return true; + } + + void read_next(std::error_code& ec) + { + if (cursor_visitor_.in_available()) + { + cursor_visitor_.send_available(ec); + } + else + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(cursor_handler_adaptor_, ec); + if (ec) return; + } + } + } + + void read_next(basic_json_visitor& visitor, std::error_code& ec) + { + { + struct resource_wrapper + { + basic_item_event_visitor_to_json_visitor& adaptor; + basic_json_visitor& original; + + resource_wrapper(basic_item_event_visitor_to_json_visitor& adaptor, + basic_json_visitor& visitor) + : adaptor(adaptor), original(adaptor.destination()) + { + adaptor.destination(visitor); + } + + ~resource_wrapper() + { + adaptor.destination(original); + } + } wrapper(cursor_handler_adaptor_, visitor); + + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(cursor_handler_adaptor_, ec); + if (ec) return; + } + } + } +}; + +using msgpack_stream_cursor = basic_msgpack_cursor; +using msgpack_bytes_cursor = basic_msgpack_cursor; + +} // namespace msgpack +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons_ext/msgpack/msgpack_encoder.hpp b/third_party/jsoncons_ext/msgpack/msgpack_encoder.hpp new file mode 100644 index 0000000000..88c74d2a88 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_encoder.hpp @@ -0,0 +1,742 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_ENCODER_HPP +#define JSONCONS_MSGPACK_MSGPACK_ENCODER_HPP + +#include +#include +#include // std::numeric_limits +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace msgpack { + + enum class msgpack_container_type {object, array}; + + template > + class basic_msgpack_encoder final : public basic_json_visitor + { + enum class decimal_parse_state { start, integer, exp1, exp2, fraction1 }; + + static constexpr int64_t nanos_in_milli = 1000000; + static constexpr int64_t nanos_in_second = 1000000000; + static constexpr int64_t millis_in_second = 1000; + public: + using allocator_type = Allocator; + using char_type = char; + using typename basic_json_visitor::string_view_type; + using sink_type = Sink; + + private: + struct stack_item + { + msgpack_container_type type_; + std::size_t length_; + std::size_t count_; + + stack_item(msgpack_container_type type, std::size_t length = 0) noexcept + : type_(type), length_(length), count_(0) + { + } + + std::size_t length() const + { + return length_; + } + + std::size_t count() const + { + return count_; + } + + bool is_object() const + { + return type_ == msgpack_container_type::object; + } + }; + + Sink sink_; + const msgpack_encode_options options_; + allocator_type alloc_; + + std::vector stack_; + int nesting_depth_; + + // Noncopyable and nonmoveable + basic_msgpack_encoder(const basic_msgpack_encoder&) = delete; + basic_msgpack_encoder& operator=(const basic_msgpack_encoder&) = delete; + public: + explicit basic_msgpack_encoder(Sink&& sink, + const Allocator& alloc = Allocator()) + : basic_msgpack_encoder(std::forward(sink), msgpack_encode_options(), alloc) + { + } + + explicit basic_msgpack_encoder(Sink&& sink, + const msgpack_encode_options& options, + const Allocator& alloc = Allocator()) + : sink_(std::forward(sink)), + options_(options), + alloc_(alloc), + nesting_depth_(0) + { + } + + ~basic_msgpack_encoder() noexcept + { + sink_.flush(); + } + + void reset() + { + stack_.clear(); + nesting_depth_ = 0; + } + + void reset(Sink&& sink) + { + sink_ = std::move(sink); + reset(); + } + + private: + // Implementing methods + + void visit_flush() final + { + sink_.flush(); + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) final + { + ec = msgpack_errc::object_length_required; + return false; + } + + bool visit_begin_object(std::size_t length, semantic_tag, const ser_context&, std::error_code& ec) final + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = msgpack_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(msgpack_container_type::object, length); + + if (length <= 15) + { + // fixmap + sink_.push_back(jsoncons::msgpack::msgpack_type::fixmap_base_type | (length & 0xf)); + } + else if (length <= 65535) + { + // map 16 + sink_.push_back(jsoncons::msgpack::msgpack_type::map16_type); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + else if (length <= (std::numeric_limits::max)()) + { + // map 32 + sink_.push_back(jsoncons::msgpack::msgpack_type::map32_type); + binary::native_to_big(static_cast(length), + std::back_inserter(sink_)); + } + + return true; + } + + bool visit_end_object(const ser_context&, std::error_code& ec) final + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + if (stack_.back().count() < stack_.back().length()) + { + ec = msgpack_errc::too_few_items; + return false; + } + else if (stack_.back().count() > stack_.back().length()) + { + ec = msgpack_errc::too_many_items; + return false; + } + + stack_.pop_back(); + end_value(); + return true; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) final + { + ec = msgpack_errc::array_length_required; + return false; + } + + bool visit_begin_array(std::size_t length, semantic_tag, const ser_context&, std::error_code& ec) final + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = msgpack_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(msgpack_container_type::array, length); + if (length <= 15) + { + // fixarray + sink_.push_back(jsoncons::msgpack::msgpack_type::fixarray_base_type | (length & 0xf)); + } + else if (length <= (std::numeric_limits::max)()) + { + // array 16 + sink_.push_back(jsoncons::msgpack::msgpack_type::array16_type); + binary::native_to_big(static_cast(length),std::back_inserter(sink_)); + } + else if (length <= (std::numeric_limits::max)()) + { + // array 32 + sink_.push_back(jsoncons::msgpack::msgpack_type::array32_type); + binary::native_to_big(static_cast(length),std::back_inserter(sink_)); + } + return true; + } + + bool visit_end_array(const ser_context&, std::error_code& ec) final + { + JSONCONS_ASSERT(!stack_.empty()); + + --nesting_depth_; + + if (stack_.back().count() < stack_.back().length()) + { + ec = msgpack_errc::too_few_items; + return false; + } + else if (stack_.back().count() > stack_.back().length()) + { + ec = msgpack_errc::too_many_items; + return false; + } + + stack_.pop_back(); + end_value(); + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code&) final + { + write_string_value(name); + return true; + } + + bool visit_null(semantic_tag, const ser_context&, std::error_code&) final + { + // nil + sink_.push_back(jsoncons::msgpack::msgpack_type::nil_type); + end_value(); + return true; + } + + void write_timestamp(int64_t seconds, int64_t nanoseconds) + { + if ((seconds >> 34) == 0) + { + uint64_t data64 = (nanoseconds << 34) | seconds; + if ((data64 & 0xffffffff00000000L) == 0) + { + // timestamp 32 + sink_.push_back(jsoncons::msgpack::msgpack_type::fixext4_type); + sink_.push_back(0xff); + binary::native_to_big(static_cast(data64), std::back_inserter(sink_)); + } + else + { + // timestamp 64 + sink_.push_back(jsoncons::msgpack::msgpack_type::fixext8_type); + sink_.push_back(0xff); + binary::native_to_big(static_cast(data64), std::back_inserter(sink_)); + } + } + else + { + // timestamp 96 + sink_.push_back(jsoncons::msgpack::msgpack_type::ext8_type); + sink_.push_back(0x0c); // 12 + sink_.push_back(0xff); + binary::native_to_big(static_cast(nanoseconds), std::back_inserter(sink_)); + binary::native_to_big(static_cast(seconds), std::back_inserter(sink_)); + } + } + + bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code& ec) final + { + switch (tag) + { + case semantic_tag::epoch_second: + { + int64_t seconds; + auto result = jsoncons::detail::to_integer(sv.data(), sv.length(), seconds); + if (!result) + { + ec = msgpack_errc::invalid_timestamp; + return false; + } + write_timestamp(seconds, 0); + break; + } + case semantic_tag::epoch_milli: + { + bigint n = bigint::from_string(sv.data(), sv.length()); + if (n != 0) + { + bigint q; + bigint rem; + n.divide(millis_in_second, q, rem, true); + int64_t seconds = static_cast(q); + int64_t nanoseconds = static_cast(rem) * nanos_in_milli; + if (nanoseconds < 0) + { + nanoseconds = -nanoseconds; + } + write_timestamp(seconds, nanoseconds); + } + else + { + write_timestamp(0, 0); + } + break; + } + case semantic_tag::epoch_nano: + { + bigint n = bigint::from_string(sv.data(), sv.length()); + if (n != 0) + { + bigint q; + bigint rem; + n.divide(nanos_in_second, q, rem, true); + int64_t seconds = static_cast(q); + int64_t nanoseconds = static_cast(rem); + if (nanoseconds < 0) + { + nanoseconds = -nanoseconds; + } + write_timestamp(seconds, nanoseconds); + } + else + { + write_timestamp(0, 0); + } + break; + } + default: + { + write_string_value(sv); + end_value(); + break; + } + } + return true; + } + + void write_string_value(const string_view_type& sv) + { + auto sink = unicode_traits::validate(sv.data(), sv.size()); + if (sink.ec != unicode_traits::conv_errc()) + { + JSONCONS_THROW(ser_error(msgpack_errc::invalid_utf8_text_string)); + } + + const size_t length = sv.length(); + if (length <= 31) + { + // fixstr stores a byte array whose length is upto 31 bytes + sink_.push_back(jsoncons::msgpack::msgpack_type::fixstr_base_type | static_cast(length)); + } + else if (length <= (std::numeric_limits::max)()) + { + // str 8 stores a byte array whose length is upto (2^8)-1 bytes + sink_.push_back(jsoncons::msgpack::msgpack_type::str8_type); + sink_.push_back(static_cast(length)); + } + else if (length <= (std::numeric_limits::max)()) + { + // str 16 stores a byte array whose length is upto (2^16)-1 bytes + sink_.push_back(jsoncons::msgpack::msgpack_type::str16_type); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); + } + else if (length <= (std::numeric_limits::max)()) + { + // str 32 stores a byte array whose length is upto (2^32)-1 bytes + sink_.push_back(jsoncons::msgpack::msgpack_type::str32_type); + binary::native_to_big(static_cast(length),std::back_inserter(sink_)); + } + + for (auto c : sv) + { + sink_.push_back(c); + } + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag, + const ser_context&, + std::error_code&) final + { + + const std::size_t length = b.size(); + if (length <= (std::numeric_limits::max)()) + { + // bin 8 stores a byte array whose length is upto (2^8)-1 bytes + sink_.push_back(jsoncons::msgpack::msgpack_type::bin8_type); + sink_.push_back(static_cast(length)); + } + else if (length <= (std::numeric_limits::max)()) + { + // bin 16 stores a byte array whose length is upto (2^16)-1 bytes + sink_.push_back(jsoncons::msgpack::msgpack_type::bin16_type); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); + } + else if (length <= (std::numeric_limits::max)()) + { + // bin 32 stores a byte array whose length is upto (2^32)-1 bytes + sink_.push_back(jsoncons::msgpack::msgpack_type::bin32_type); + binary::native_to_big(static_cast(length),std::back_inserter(sink_)); + } + + for (auto c : b) + { + sink_.push_back(c); + } + + end_value(); + return true; + } + + bool visit_byte_string(const byte_string_view& b, + uint64_t ext_tag, + const ser_context&, + std::error_code&) final + { + const std::size_t length = b.size(); + switch (length) + { + case 1: + sink_.push_back(jsoncons::msgpack::msgpack_type::fixext1_type); + sink_.push_back(static_cast(ext_tag)); + break; + case 2: + sink_.push_back(jsoncons::msgpack::msgpack_type::fixext2_type); + sink_.push_back(static_cast(ext_tag)); + break; + case 4: + sink_.push_back(jsoncons::msgpack::msgpack_type::fixext4_type); + sink_.push_back(static_cast(ext_tag)); + break; + case 8: + sink_.push_back(jsoncons::msgpack::msgpack_type::fixext8_type); + sink_.push_back(static_cast(ext_tag)); + break; + case 16: + sink_.push_back(jsoncons::msgpack::msgpack_type::fixext16_type); + sink_.push_back(static_cast(ext_tag)); + break; + default: + if (length <= (std::numeric_limits::max)()) + { + sink_.push_back(jsoncons::msgpack::msgpack_type::ext8_type); + sink_.push_back(static_cast(length)); + sink_.push_back(static_cast(ext_tag)); + } + else if (length <= (std::numeric_limits::max)()) + { + sink_.push_back(jsoncons::msgpack::msgpack_type::ext16_type); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); + sink_.push_back(static_cast(ext_tag)); + } + else if (length <= (std::numeric_limits::max)()) + { + sink_.push_back(jsoncons::msgpack::msgpack_type::ext32_type); + binary::native_to_big(static_cast(length),std::back_inserter(sink_)); + sink_.push_back(static_cast(ext_tag)); + } + break; + } + + for (auto c : b) + { + sink_.push_back(c); + } + + end_value(); + return true; + } + + bool visit_double(double val, + semantic_tag, + const ser_context&, + std::error_code&) final + { + float valf = (float)val; + if ((double)valf == val) + { + // float 32 + sink_.push_back(jsoncons::msgpack::msgpack_type::float32_type); + binary::native_to_big(valf,std::back_inserter(sink_)); + } + else + { + // float 64 + sink_.push_back(jsoncons::msgpack::msgpack_type::float64_type); + binary::native_to_big(val,std::back_inserter(sink_)); + } + + // write double + + end_value(); + return true; + } + + bool visit_int64(int64_t val, + semantic_tag tag, + const ser_context&, + std::error_code&) final + { + switch (tag) + { + case semantic_tag::epoch_second: + write_timestamp(val, 0); + break; + case semantic_tag::epoch_milli: + { + if (val != 0) + { + auto dv = std::div(val,millis_in_second); + int64_t seconds = dv.quot; + int64_t nanoseconds = dv.rem*nanos_in_milli; + if (nanoseconds < 0) + { + nanoseconds = -nanoseconds; + } + write_timestamp(seconds, nanoseconds); + } + else + { + write_timestamp(0, 0); + } + break; + } + case semantic_tag::epoch_nano: + { + if (val != 0) + { + auto dv = std::div(val,static_cast(nanos_in_second)); + int64_t seconds = dv.quot; + int64_t nanoseconds = dv.rem; + if (nanoseconds < 0) + { + nanoseconds = -nanoseconds; + } + write_timestamp(seconds, nanoseconds); + } + else + { + write_timestamp(0, 0); + } + break; + } + default: + { + if (val >= 0) + { + if (val <= 0x7f) + { + // positive fixnum stores 7-bit positive integer + sink_.push_back(static_cast(val)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 8 stores a 8-bit unsigned integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint8_type); + sink_.push_back(static_cast(val)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 16 stores a 16-bit big-endian unsigned integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint16_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 32 stores a 32-bit big-endian unsigned integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint32_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= (std::numeric_limits::max)()) + { + // int 64 stores a 64-bit big-endian signed integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint64_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + } + else + { + if (val >= -32) + { + // negative fixnum stores 5-bit negative integer + binary::native_to_big(static_cast(val), std::back_inserter(sink_)); + } + else if (val >= (std::numeric_limits::lowest)()) + { + // int 8 stores a 8-bit signed integer + sink_.push_back(jsoncons::msgpack::msgpack_type::int8_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val >= (std::numeric_limits::lowest)()) + { + // int 16 stores a 16-bit big-endian signed integer + sink_.push_back(jsoncons::msgpack::msgpack_type::int16_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val >= (std::numeric_limits::lowest)()) + { + // int 32 stores a 32-bit big-endian signed integer + sink_.push_back(jsoncons::msgpack::msgpack_type::int32_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val >= (std::numeric_limits::lowest)()) + { + // int 64 stores a 64-bit big-endian signed integer + sink_.push_back(jsoncons::msgpack::msgpack_type::int64_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + } + } + break; + } + end_value(); + return true; + } + + bool visit_uint64(uint64_t val, + semantic_tag tag, + const ser_context&, + std::error_code&) final + { + switch (tag) + { + case semantic_tag::epoch_second: + write_timestamp(static_cast(val), 0); + break; + case semantic_tag::epoch_milli: + { + if (val != 0) + { + auto dv = std::div(static_cast(val), static_cast(millis_in_second)); + int64_t seconds = dv.quot; + int64_t nanoseconds = dv.rem*nanos_in_milli; + if (nanoseconds < 0) + { + nanoseconds = -nanoseconds; + } + write_timestamp(seconds, nanoseconds); + } + else + { + write_timestamp(0, 0); + } + break; + } + case semantic_tag::epoch_nano: + { + if (val != 0) + { + auto dv = std::div(static_cast(val), static_cast(nanos_in_second)); + int64_t seconds = dv.quot; + int64_t nanoseconds = dv.rem; + if (nanoseconds < 0) + { + nanoseconds = -nanoseconds; + } + write_timestamp(seconds, nanoseconds); + } + else + { + write_timestamp(0, 0); + } + break; + } + default: + { + if (val <= static_cast((std::numeric_limits::max)())) + { + // positive fixnum stores 7-bit positive integer + sink_.push_back(static_cast(val)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 8 stores a 8-bit unsigned integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint8_type); + sink_.push_back(static_cast(val)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 16 stores a 16-bit big-endian unsigned integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint16_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 32 stores a 32-bit big-endian unsigned integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint32_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 64 stores a 64-bit big-endian unsigned integer + sink_.push_back(jsoncons::msgpack::msgpack_type::uint64_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + break; + } + } + end_value(); + return true; + } + + bool visit_bool(bool val, semantic_tag, const ser_context&, std::error_code&) final + { + // true and false + sink_.push_back(static_cast(val ? jsoncons::msgpack::msgpack_type::true_type : jsoncons::msgpack::msgpack_type::false_type)); + + end_value(); + return true; + } + + void end_value() + { + if (!stack_.empty()) + { + ++stack_.back().count_; + } + } + }; + + using msgpack_stream_encoder = basic_msgpack_encoder; + using msgpack_bytes_encoder = basic_msgpack_encoder>>; + +} // namespace msgpack +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/msgpack/msgpack_error.hpp b/third_party/jsoncons_ext/msgpack/msgpack_error.hpp new file mode 100644 index 0000000000..ea172fa694 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_error.hpp @@ -0,0 +1,94 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_ERROR_HPP +#define JSONCONS_MSGPACK_MSGPACK_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace msgpack { + +enum class msgpack_errc +{ + success = 0, + unexpected_eof = 1, + source_error, + invalid_utf8_text_string, + array_length_required, + object_length_required, + too_many_items, + too_few_items, + max_nesting_depth_exceeded, + length_is_negative, + invalid_timestamp, + unknown_type +}; + +class msgpack_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/msgpack"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case msgpack_errc::unexpected_eof: + return "Unexpected end of file"; + case msgpack_errc::source_error: + return "Source error"; + case msgpack_errc::invalid_utf8_text_string: + return "Illegal UTF-8 encoding in text string"; + case msgpack_errc::array_length_required: + return "MessagePack encoder requires array length"; + case msgpack_errc::object_length_required: + return "MessagePack encoder requires object length"; + case msgpack_errc::too_many_items: + return "Too many items were added to a MessagePack object or array"; + case msgpack_errc::too_few_items: + return "Too few items were added to a MessagePack object or array"; + case msgpack_errc::max_nesting_depth_exceeded: + return "Data item nesting exceeds limit in options"; + case msgpack_errc::length_is_negative: + return "Request for the length of an array, map or string returned a negative result"; + case msgpack_errc::invalid_timestamp: + return "Invalid timestamp"; + case msgpack_errc::unknown_type: + return "An unknown type was found in the stream"; + default: + return "Unknown MessagePack parser error"; + } + } +}; + +inline +const std::error_category& msgpack_error_category() +{ + static msgpack_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(msgpack_errc e) +{ + return std::error_code(static_cast(e),msgpack_error_category()); +} + + +}} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons_ext/msgpack/msgpack_event_reader.hpp b/third_party/jsoncons_ext/msgpack/msgpack_event_reader.hpp new file mode 100644 index 0000000000..b04db58e58 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_event_reader.hpp @@ -0,0 +1,256 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_EVENT_READER_HPP +#define JSONCONS_MSGPACK_MSGPACK_EVENT_READER_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace msgpack { + + template > + class msgpack_event_reader : public basic_staj_event_reader, private virtual ser_context + { + public: + using source_type = Source; + using char_type = char; + using allocator_type = Allocator; + private: + basic_msgpack_parser parser_; + basic_item_event_receiver event_receiver_; + bool eof_; + + // Noncopyable and nonmoveable + msgpack_event_reader(const msgpack_event_reader&) = delete; + msgpack_event_reader& operator=(const msgpack_event_reader&) = delete; + + public: + using string_view_type = string_view; + + template + msgpack_event_reader(Sourceable&& source, + const msgpack_decode_options& options = msgpack_decode_options(), + const Allocator& alloc = Allocator()) + : parser_(std::forward(source), options, alloc), + event_receiver_(accept_all), + eof_(false) + { + if (!done()) + { + next(); + } + } + + // Constructors that set parse error codes + + template + msgpack_event_reader(Sourceable&& source, + std::error_code& ec) + : msgpack_event_reader(std::allocator_arg, Allocator(), + std::forward(source), + msgpack_decode_options(), + ec) + { + } + + template + msgpack_event_reader(Sourceable&& source, + const msgpack_decode_options& options, + std::error_code& ec) + : msgpack_event_reader(std::allocator_arg, Allocator(), + std::forward(source), + options, + ec) + { + } + + template + msgpack_event_reader(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const msgpack_decode_options& options, + std::error_code& ec) + : parser_(std::forward(source), options, alloc), + event_receiver_(accept_all), + eof_(false) + { + if (!done()) + { + next(ec); + } + } + + void reset() + { + parser_.reset(); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + template + void reset(Sourceable&& source) + { + parser_.reset(std::forward(source)); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + void reset(std::error_code& ec) + { + parser_.reset(); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + template + void reset(Sourceable&& source, std::error_code& ec) + { + parser_.reset(std::forward(source)); + event_receiver_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + bool done() const override + { + return parser_.done(); + } + + const basic_staj_event& current() const override + { + return event_receiver_.event(); + } + + void read_to(basic_item_event_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_item_event_visitor& visitor, + std::error_code& ec) override + { + if (event_receiver_.dump(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + const ser_context& context() const override + { + return *this; + } + + bool eof() const + { + return eof_; + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + staj2_filter_view operator|(msgpack_event_reader& cursor, + std::function pred) + { + return staj2_filter_view(cursor, pred); + } + + private: + static bool accept_all(const item_event&, const ser_context&) + { + return true; + } + + void read_next(std::error_code& ec) + { + if (event_receiver_.in_available()) + { + event_receiver_.send_available(ec); + } + else + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(event_receiver_, ec); + if (ec) return; + } + } + } + + void read_next(basic_item_event_visitor& visitor, std::error_code& ec) + { + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(visitor, ec); + if (ec) return; + } + } + } + }; + +} // namespace msgpack +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons_ext/msgpack/msgpack_options.hpp b/third_party/jsoncons_ext/msgpack/msgpack_options.hpp new file mode 100644 index 0000000000..844ca95790 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_options.hpp @@ -0,0 +1,74 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_OPTIONS_HPP +#define JSONCONS_MSGPACK_MSGPACK_OPTIONS_HPP + +#include +#include // std::numeric_limits +#include +#include + +namespace jsoncons { namespace msgpack { + +class msgpack_options; + +class msgpack_options_common +{ + friend class msgpack_options; + + int max_nesting_depth_; +protected: + virtual ~msgpack_options_common() = default; + + msgpack_options_common() + : max_nesting_depth_(1024) + { + } + + msgpack_options_common(const msgpack_options_common&) = default; + msgpack_options_common& operator=(const msgpack_options_common&) = default; + msgpack_options_common(msgpack_options_common&&) = default; + msgpack_options_common& operator=(msgpack_options_common&&) = default; +public: + int max_nesting_depth() const + { + return max_nesting_depth_; + } +}; + +class msgpack_decode_options : public virtual msgpack_options_common +{ + friend class msgpack_options; +public: + msgpack_decode_options() + { + } +}; + +class msgpack_encode_options : public virtual msgpack_options_common +{ + friend class msgpack_options; +public: + msgpack_encode_options() + { + } +}; + +class msgpack_options final : public msgpack_decode_options, public msgpack_encode_options +{ +public: + using msgpack_options_common::max_nesting_depth; + + msgpack_options& max_nesting_depth(int value) + { + this->max_nesting_depth_ = value; + return *this; + } +}; + +}} +#endif diff --git a/third_party/jsoncons_ext/msgpack/msgpack_parser.hpp b/third_party/jsoncons_ext/msgpack/msgpack_parser.hpp new file mode 100644 index 0000000000..47afe61745 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_parser.hpp @@ -0,0 +1,748 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_PARSER_HPP +#define JSONCONS_MSGPACK_MSGPACK_PARSER_HPP + +#include +#include +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace msgpack { + +enum class parse_mode {root,accept,array,map_key,map_value}; + +struct parse_state +{ + parse_mode mode; + std::size_t length; + std::size_t index; + + parse_state(parse_mode mode, std::size_t length) noexcept + : mode(mode), length(length), index(0) + { + } + + parse_state(const parse_state&) = default; + parse_state(parse_state&&) = default; +}; + +template > +class basic_msgpack_parser : public ser_context +{ + using char_type = char; + using char_traits_type = std::char_traits; + using temp_allocator_type = Allocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using int64_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using parse_state_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + static constexpr int64_t nanos_in_second = 1000000000; + + Source source_; + msgpack_decode_options options_; + bool more_; + bool done_; + std::basic_string,char_allocator_type> text_buffer_; + std::vector bytes_buffer_; + std::vector state_stack_; + int nesting_depth_; + +public: + template + basic_msgpack_parser(Sourceable&& source, + const msgpack_decode_options& options = msgpack_decode_options(), + const Allocator& alloc = Allocator()) + : source_(std::forward(source)), + options_(options), + more_(true), + done_(false), + text_buffer_(alloc), + bytes_buffer_(alloc), + state_stack_(alloc), + nesting_depth_(0) + { + state_stack_.emplace_back(parse_mode::root,0); + } + + void restart() + { + more_ = true; + } + + void reset() + { + more_ = true; + done_ = false; + text_buffer_.clear(); + bytes_buffer_.clear(); + state_stack_.clear(); + state_stack_.emplace_back(parse_mode::root,0); + nesting_depth_ = 0; + } + + template + void reset(Sourceable&& source) + { + source_ = std::forward(source); + reset(); + } + + bool done() const + { + return done_; + } + + bool stopped() const + { + return !more_; + } + + std::size_t line() const override + { + return 0; + } + + std::size_t column() const override + { + return source_.position(); + } + + void parse(item_event_visitor& visitor, std::error_code& ec) + { + while (!done_ && more_) + { + switch (state_stack_.back().mode) + { + case parse_mode::array: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + read_item(visitor, ec); + if (ec) + { + return; + } + } + else + { + end_array(visitor, ec); + } + break; + } + case parse_mode::map_key: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + state_stack_.back().mode = parse_mode::map_value; + read_item(visitor, ec); + if (ec) + { + return; + } + } + else + { + end_object(visitor, ec); + } + break; + } + case parse_mode::map_value: + { + state_stack_.back().mode = parse_mode::map_key; + read_item(visitor, ec); + if (ec) + { + return; + } + break; + } + case parse_mode::root: + { + state_stack_.back().mode = parse_mode::accept; + read_item(visitor, ec); + if (ec) + { + return; + } + break; + } + case parse_mode::accept: + { + JSONCONS_ASSERT(state_stack_.size() == 1); + state_stack_.clear(); + more_ = false; + done_ = true; + visitor.flush(); + break; + } + } + } + } +private: + + void read_item(item_event_visitor& visitor, std::error_code& ec) + { + if (source_.is_error()) + { + ec = msgpack_errc::source_error; + more_ = false; + return; + } + + uint8_t type; + if (source_.read(&type, 1) == 0) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + + if (type <= 0xbf) + { + if (type <= 0x7f) + { + // positive fixint + more_ = visitor.uint64_value(type, semantic_tag::none, *this, ec); + } + else if (type <= 0x8f) + { + begin_object(visitor,type,ec); // fixmap + } + else if (type <= 0x9f) + { + begin_array(visitor,type,ec); // fixarray + } + else + { + // fixstr + const size_t len = type & 0x1f; + + text_buffer_.clear(); + + if (source_reader::read(source_,text_buffer_,len) != static_cast(len)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + + auto result = unicode_traits::validate(text_buffer_.data(),text_buffer_.size()); + if (result.ec != unicode_traits::conv_errc()) + { + ec = msgpack_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.string_value(jsoncons::basic_string_view(text_buffer_.data(),text_buffer_.length()), semantic_tag::none, *this, ec); + } + } + else if (type >= 0xe0) + { + // negative fixint + more_ = visitor.int64_value(static_cast(type), semantic_tag::none, *this, ec); + } + else + { + switch (type) + { + case jsoncons::msgpack::msgpack_type::nil_type: + { + more_ = visitor.null_value(semantic_tag::none, *this, ec); + break; + } + case jsoncons::msgpack::msgpack_type::true_type: + { + more_ = visitor.bool_value(true, semantic_tag::none, *this, ec); + break; + } + case jsoncons::msgpack::msgpack_type::false_type: + { + more_ = visitor.bool_value(false, semantic_tag::none, *this, ec); + break; + } + case jsoncons::msgpack::msgpack_type::float32_type: + { + uint8_t buf[sizeof(float)]; + if (source_.read(buf, sizeof(float)) != sizeof(float)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + float val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.double_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::float64_type: + { + uint8_t buf[sizeof(double)]; + if (source_.read(buf, sizeof(double)) != sizeof(double)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + double val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.double_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::uint8_type: + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + more_ = visitor.uint64_value(b, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::uint16_type: + { + uint8_t buf[sizeof(uint16_t)]; + if (source_.read(buf, sizeof(uint16_t)) !=sizeof(uint16_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + uint16_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.uint64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::uint32_type: + { + uint8_t buf[sizeof(uint32_t)]; + if (source_.read(buf, sizeof(uint32_t)) != sizeof(uint32_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + uint32_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.uint64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::uint64_type: + { + uint8_t buf[sizeof(uint64_t)]; + if (source_.read(buf, sizeof(uint64_t)) != sizeof(uint64_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + uint64_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.uint64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::int8_type: + { + uint8_t buf[sizeof(int8_t)]; + if (source_.read(buf, sizeof(int8_t)) != sizeof(int8_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + int8_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::int16_type: + { + uint8_t buf[sizeof(int16_t)]; + if (source_.read(buf, sizeof(int16_t)) != sizeof(int16_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + int16_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::int32_type: + { + uint8_t buf[sizeof(int32_t)]; + if (source_.read(buf, sizeof(int32_t)) != sizeof(int32_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + int32_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::int64_type: + { + uint8_t buf[sizeof(int64_t)]; + if (source_.read(buf, sizeof(int64_t)) != sizeof(int64_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + int64_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::str8_type: + case jsoncons::msgpack::msgpack_type::str16_type: + case jsoncons::msgpack::msgpack_type::str32_type: + { + std::size_t len = get_size(type, ec); + if (!more_) + { + return; + } + + text_buffer_.clear(); + if (source_reader::read(source_,text_buffer_,len) != static_cast(len)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + + auto result = unicode_traits::validate(text_buffer_.data(),text_buffer_.size()); + if (result.ec != unicode_traits::conv_errc()) + { + ec = msgpack_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.string_value(jsoncons::basic_string_view(text_buffer_.data(),text_buffer_.length()), semantic_tag::none, *this, ec); + break; + } + + case jsoncons::msgpack::msgpack_type::bin8_type: + case jsoncons::msgpack::msgpack_type::bin16_type: + case jsoncons::msgpack::msgpack_type::bin32_type: + { + std::size_t len = get_size(type,ec); + if (!more_) + { + return; + } + bytes_buffer_.clear(); + if (source_reader::read(source_,bytes_buffer_,len) != static_cast(len)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + + more_ = visitor.byte_string_value(byte_string_view(bytes_buffer_.data(),bytes_buffer_.size()), + semantic_tag::none, + *this, + ec); + break; + } + case jsoncons::msgpack::msgpack_type::fixext1_type: + case jsoncons::msgpack::msgpack_type::fixext2_type: + case jsoncons::msgpack::msgpack_type::fixext4_type: + case jsoncons::msgpack::msgpack_type::fixext8_type: + case jsoncons::msgpack::msgpack_type::fixext16_type: + case jsoncons::msgpack::msgpack_type::ext8_type: + case jsoncons::msgpack::msgpack_type::ext16_type: + case jsoncons::msgpack::msgpack_type::ext32_type: + { + std::size_t len = get_size(type,ec); + if (!more_) + { + return; + } + + // type + uint8_t buf[sizeof(int8_t)]; + if (source_.read(buf, sizeof(int8_t)) != sizeof(int8_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + + int8_t ext_type = binary::big_to_native(buf, sizeof(buf)); + + bool is_timestamp = false; + if (ext_type == -1) + { + is_timestamp = true;; + } + + // payload + if (is_timestamp && len == 4) + { + uint8_t buf32[sizeof(uint32_t)]; + if (source_.read(buf32, sizeof(uint32_t)) != sizeof(uint32_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + uint32_t val = binary::big_to_native(buf32, sizeof(buf32)); + more_ = visitor.uint64_value(val, semantic_tag::epoch_second, *this, ec); + } + else if (is_timestamp && len == 8) + { + uint8_t buf64[sizeof(uint64_t)]; + if (source_.read(buf64, sizeof(uint64_t)) != sizeof(uint64_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + uint64_t data64 = binary::big_to_native(buf64, sizeof(buf64)); + uint64_t sec = data64 & 0x00000003ffffffffL; + uint64_t nsec = data64 >> 34; + + bigint nano(sec); + nano *= uint64_t(nanos_in_second); + nano += nsec; + text_buffer_.clear(); + nano.write_string(text_buffer_); + more_ = visitor.string_value(text_buffer_, semantic_tag::epoch_nano, *this, ec); + if (!more_) return; + } + else if (is_timestamp && len == 12) + { + uint8_t buf1[sizeof(uint32_t)]; + if (source_.read(buf1, sizeof(uint32_t)) != sizeof(uint32_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + uint32_t nsec = binary::big_to_native(buf1, sizeof(buf1)); + + uint8_t buf2[sizeof(int64_t)]; + if (source_.read(buf2, sizeof(int64_t)) != sizeof(int64_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + int64_t sec = binary::big_to_native(buf2, sizeof(buf2)); + + bigint nano(sec); + + nano *= uint64_t(nanos_in_second); + + if (nano < 0) + { + nano -= nsec; + } + else + { + nano += nsec; + } + + text_buffer_.clear(); + nano.write_string(text_buffer_); + more_ = visitor.string_value(text_buffer_, semantic_tag::epoch_nano, *this, ec); + if (!more_) return; + } + else + { + bytes_buffer_.clear(); + if (source_reader::read(source_,bytes_buffer_,len) != static_cast(len)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return; + } + + more_ = visitor.byte_string_value(byte_string_view(bytes_buffer_.data(),bytes_buffer_.size()), + static_cast(ext_type), + *this, + ec); + } + break; + } + + case jsoncons::msgpack::msgpack_type::array16_type: + case jsoncons::msgpack::msgpack_type::array32_type: + { + begin_array(visitor,type,ec); + break; + } + + case jsoncons::msgpack::msgpack_type::map16_type : + case jsoncons::msgpack::msgpack_type::map32_type : + { + begin_object(visitor, type, ec); + break; + } + + default: + { + ec = msgpack_errc::unknown_type; + more_ = false; + return; + } + } + } + } + + void begin_array(item_event_visitor& visitor, uint8_t type, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = msgpack_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + std::size_t length = get_size(type, ec); + if (!more_) + { + return; + } + state_stack_.emplace_back(parse_mode::array,length); + more_ = visitor.begin_array(length, semantic_tag::none, *this, ec); + } + + void end_array(item_event_visitor& visitor, std::error_code& ec) + { + --nesting_depth_; + + more_ = visitor.end_array(*this, ec); + state_stack_.pop_back(); + } + + void begin_object(item_event_visitor& visitor, uint8_t type, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = msgpack_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + std::size_t length = get_size(type, ec); + if (!more_) + { + return; + } + state_stack_.emplace_back(parse_mode::map_key,length); + more_ = visitor.begin_object(length, semantic_tag::none, *this, ec); + } + + void end_object(item_event_visitor& visitor, std::error_code& ec) + { + --nesting_depth_; + more_ = visitor.end_object(*this, ec); + state_stack_.pop_back(); + } + + std::size_t get_size(uint8_t type, std::error_code& ec) + { + switch (type) + { + case jsoncons::msgpack::msgpack_type::str8_type: + case jsoncons::msgpack::msgpack_type::bin8_type: + case jsoncons::msgpack::msgpack_type::ext8_type: + { + uint8_t buf[sizeof(int8_t)]; + if (source_.read(buf, sizeof(int8_t)) != sizeof(int8_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return 0; + } + uint8_t len = binary::big_to_native(buf, sizeof(buf)); + return static_cast(len); + } + + case jsoncons::msgpack::msgpack_type::str16_type: + case jsoncons::msgpack::msgpack_type::bin16_type: + case jsoncons::msgpack::msgpack_type::ext16_type: + case jsoncons::msgpack::msgpack_type::array16_type: + case jsoncons::msgpack::msgpack_type::map16_type: + { + uint8_t buf[sizeof(int16_t)]; + if (source_.read(buf, sizeof(int16_t)) != sizeof(int16_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return 0; + } + uint16_t len = binary::big_to_native(buf, sizeof(buf)); + return static_cast(len); + } + + case jsoncons::msgpack::msgpack_type::str32_type: + case jsoncons::msgpack::msgpack_type::bin32_type: + case jsoncons::msgpack::msgpack_type::ext32_type: + case jsoncons::msgpack::msgpack_type::array32_type: + case jsoncons::msgpack::msgpack_type::map32_type : + { + uint8_t buf[sizeof(int32_t)]; + if (source_.read(buf, sizeof(int32_t)) != sizeof(int32_t)) + { + ec = msgpack_errc::unexpected_eof; + more_ = false; + return 0; + } + uint32_t len = binary::big_to_native(buf, sizeof(buf)); + return static_cast(len); + } + case jsoncons::msgpack::msgpack_type::fixext1_type: + return 1; + case jsoncons::msgpack::msgpack_type::fixext2_type: + return 2; + case jsoncons::msgpack::msgpack_type::fixext4_type: + return 4; + case jsoncons::msgpack::msgpack_type::fixext8_type: + return 8; + case jsoncons::msgpack::msgpack_type::fixext16_type: + return 16; + default: + if ((type > 0x8f && type <= 0x9f) // fixarray + || (type > 0x7f && type <= 0x8f) // fixmap + ) + { + return type & 0x0f; + } + else + { + ec = msgpack_errc::unknown_type; + more_ = false; + return 0; + } + break; + } + } +}; + +}} + +#endif diff --git a/third_party/jsoncons_ext/msgpack/msgpack_reader.hpp b/third_party/jsoncons_ext/msgpack/msgpack_reader.hpp new file mode 100644 index 0000000000..ec1dbcace6 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_reader.hpp @@ -0,0 +1,111 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_READER_HPP +#define JSONCONS_MSGPACK_MSGPACK_READER_HPP + +#include +#include +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace msgpack { + +template > +class basic_msgpack_reader +{ + using char_type = char; + + basic_msgpack_parser parser_; + basic_item_event_visitor_to_json_visitor adaptor_; + item_event_visitor& visitor_; +public: + template + basic_msgpack_reader(Sourceable&& source, + json_visitor& visitor, + const Allocator& alloc) + : basic_msgpack_reader(std::forward(source), + visitor, + msgpack_decode_options(), + alloc) + { + } + + template + basic_msgpack_reader(Sourceable&& source, + json_visitor& visitor, + const msgpack_decode_options& options = msgpack_decode_options(), + const Allocator& alloc=Allocator()) + : parser_(std::forward(source), options, alloc), + adaptor_(visitor, alloc), visitor_(adaptor_) + { + } + template + basic_msgpack_reader(Sourceable&& source, + item_event_visitor& visitor, + const Allocator& alloc) + : basic_msgpack_reader(std::forward(source), + visitor, + msgpack_decode_options(), + alloc) + { + } + + template + basic_msgpack_reader(Sourceable&& source, + item_event_visitor& visitor, + const msgpack_decode_options& options = msgpack_decode_options(), + const Allocator& alloc=Allocator()) + : parser_(std::forward(source), options, alloc), + visitor_(visitor) + { + } + + void read() + { + std::error_code ec; + read(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line(),column())); + } + } + + void read(std::error_code& ec) + { + parser_.reset(); + parser_.parse(visitor_, ec); + if (ec) + { + return; + } + } + + std::size_t line() const + { + return parser_.line(); + } + + std::size_t column() const + { + return parser_.column(); + } +}; + +using msgpack_stream_reader = basic_msgpack_reader; + +using msgpack_bytes_reader = basic_msgpack_reader; + +}} + +#endif diff --git a/third_party/jsoncons_ext/msgpack/msgpack_type.hpp b/third_party/jsoncons_ext/msgpack/msgpack_type.hpp new file mode 100644 index 0000000000..538b11bd99 --- /dev/null +++ b/third_party/jsoncons_ext/msgpack/msgpack_type.hpp @@ -0,0 +1,63 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_MSGPACK_MSGPACK_TYPE_HPP +#define JSONCONS_MSGPACK_MSGPACK_TYPE_HPP + +#include +#include +#include + +namespace jsoncons { namespace msgpack { + + namespace msgpack_type + { + const uint8_t positive_fixint_base_type = 0x00; + const uint8_t nil_type = 0xc0; + const uint8_t false_type = 0xc2; + const uint8_t true_type = 0xc3; + const uint8_t float32_type = 0xca; + const uint8_t float64_type = 0xcb; + const uint8_t uint8_type = 0xcc; + const uint8_t uint16_type = 0xcd; + const uint8_t uint32_type = 0xce; + const uint8_t uint64_type = 0xcf; + const uint8_t int8_type = 0xd0; + const uint8_t int16_type = 0xd1; + const uint8_t int32_type = 0xd2; + const uint8_t int64_type = 0xd3; + + const uint8_t fixmap_base_type = 0x80; + const uint8_t fixarray_base_type = 0x90; + const uint8_t fixstr_base_type = 0xa0; + const uint8_t str8_type = 0xd9; + const uint8_t str16_type = 0xda; + const uint8_t str32_type = 0xdb; + + const uint8_t bin8_type = 0xc4; // 0xC4 + const uint8_t bin16_type = 0xc5; + const uint8_t bin32_type = 0xc6; + + const uint8_t fixext1_type = 0xd4; + const uint8_t fixext2_type = 0xd5; + const uint8_t fixext4_type = 0xd6; + const uint8_t fixext8_type = 0xd7; + const uint8_t fixext16_type = 0xd8; + const uint8_t ext8_type = 0xc7; // 0xC4 + const uint8_t ext16_type = 0xc8; + const uint8_t ext32_type = 0xc9; + + const uint8_t array16_type = 0xdc; + const uint8_t array32_type = 0xdd; + const uint8_t map16_type = 0xde; + const uint8_t map32_type = 0xdf; + const uint8_t negative_fixint_base_type = 0xe0; + } + +} // namespace msgpack +} // namespace jsoncons + +#endif diff --git a/third_party/jsoncons_ext/ubjson/decode_ubjson.hpp b/third_party/jsoncons_ext/ubjson/decode_ubjson.hpp new file mode 100644 index 0000000000..fb84b52d2b --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/decode_ubjson.hpp @@ -0,0 +1,202 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_DECODE_UBJSON_HPP +#define JSONCONS_UBJSON_DECODE_UBJSON_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace ubjson { + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_ubjson(const Source& v, + const ubjson_decode_options& options = ubjson_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_ubjson_reader reader(v, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_ubjson(const Source& v, + const ubjson_decode_options& options = ubjson_decode_options()) + { + basic_ubjson_cursor cursor(v, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_ubjson(std::istream& is, + const ubjson_decode_options& options = ubjson_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + ubjson_stream_reader reader(is, adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_ubjson(std::istream& is, + const ubjson_decode_options& options = ubjson_decode_options()) + { + basic_ubjson_cursor cursor(is, options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_ubjson(InputIt first, InputIt last, + const ubjson_decode_options& options = ubjson_decode_options()) + { + jsoncons::json_decoder decoder; + auto adaptor = make_json_visitor_adaptor(decoder); + basic_ubjson_reader> reader(binary_iterator_source(first, last), adaptor, options); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_ubjson(InputIt first, InputIt last, + const ubjson_decode_options& options = ubjson_decode_options()) + { + basic_ubjson_cursor> cursor(binary_iterator_source(first, last), options); + json_decoder> decoder{}; + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + // With leading allocator_set parameter + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_ubjson(const allocator_set& alloc_set, + const Source& v, + const ubjson_decode_options& options = ubjson_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_ubjson_reader reader(v, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value && + extension_traits::is_byte_sequence::value,T>::type + decode_ubjson(const allocator_set& alloc_set, + const Source& v, + const ubjson_decode_options& options = ubjson_decode_options()) + { + basic_ubjson_cursor cursor(v, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + + template + typename std::enable_if::value,T>::type + decode_ubjson(const allocator_set& alloc_set, + std::istream& is, + const ubjson_decode_options& options = ubjson_decode_options()) + { + json_decoder decoder(alloc_set.get_allocator(), alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor(decoder); + basic_ubjson_reader reader(is, adaptor, options, alloc_set.get_temp_allocator()); + reader.read(); + if (!decoder.is_valid()) + { + JSONCONS_THROW(ser_error(conv_errc::conversion_failed, reader.line(), reader.column())); + } + return decoder.get_result(); + } + + template + typename std::enable_if::value,T>::type + decode_ubjson(const allocator_set& alloc_set, + std::istream& is, + const ubjson_decode_options& options = ubjson_decode_options()) + { + basic_ubjson_cursor cursor(is, options, alloc_set.get_temp_allocator()); + json_decoder,TempAllocator> decoder(alloc_set.get_temp_allocator(), alloc_set.get_temp_allocator()); + + std::error_code ec; + T val = decode_traits::decode(cursor, decoder, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec, cursor.context().line(), cursor.context().column())); + } + return val; + } + +} // ubjson +} // jsoncons + +#endif diff --git a/third_party/jsoncons_ext/ubjson/encode_ubjson.hpp b/third_party/jsoncons_ext/ubjson/encode_ubjson.hpp new file mode 100644 index 0000000000..0bdaa3e050 --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/encode_ubjson.hpp @@ -0,0 +1,142 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_ENCODE_UBJSON_HPP +#define JSONCONS_UBJSON_ENCODE_UBJSON_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include + +namespace jsoncons { +namespace ubjson { + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_ubjson(const T& j, + ByteContainer& cont, + const ubjson_encode_options& options = ubjson_encode_options()) + { + using char_type = typename T::char_type; + basic_ubjson_encoder> encoder(cont, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_ubjson(const T& val, + ByteContainer& cont, + const ubjson_encode_options& options = ubjson_encode_options()) + { + basic_ubjson_encoder> encoder(cont, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_ubjson(const T& j, + std::ostream& os, + const ubjson_encode_options& options = ubjson_encode_options()) + { + using char_type = typename T::char_type; + ubjson_stream_encoder encoder(os, options); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_ubjson(const T& val, + std::ostream& os, + const ubjson_encode_options& options = ubjson_encode_options()) + { + ubjson_stream_encoder encoder(os, options); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + // with temp_allocator_arg_t + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_ubjson(const allocator_set& alloc_set,const T& j, + ByteContainer& cont, + const ubjson_encode_options& options = ubjson_encode_options()) + { + using char_type = typename T::char_type; + basic_ubjson_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value && + extension_traits::is_back_insertable_byte_container::value,void>::type + encode_ubjson(const allocator_set& alloc_set,const T& val, + ByteContainer& cont, + const ubjson_encode_options& options = ubjson_encode_options()) + { + basic_ubjson_encoder,TempAllocator> encoder(cont, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + + template + typename std::enable_if::value,void>::type + encode_ubjson(const allocator_set& alloc_set, + const T& j, + std::ostream& os, + const ubjson_encode_options& options = ubjson_encode_options()) + { + using char_type = typename T::char_type; + basic_ubjson_encoder encoder(os, options, alloc_set.get_temp_allocator()); + auto adaptor = make_json_visitor_adaptor>(encoder); + j.dump(adaptor); + } + + template + typename std::enable_if::value,void>::type + encode_ubjson(const allocator_set& alloc_set, + const T& val, + std::ostream& os, + const ubjson_encode_options& options = ubjson_encode_options()) + { + basic_ubjson_encoder encoder(os, options, alloc_set.get_temp_allocator()); + std::error_code ec; + encode_traits::encode(val, encoder, json(), ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec)); + } + } + +} // ubjson +} // jsoncons + +#endif diff --git a/third_party/jsoncons_ext/ubjson/ubjson.hpp b/third_party/jsoncons_ext/ubjson/ubjson.hpp new file mode 100644 index 0000000000..8a194a277d --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson.hpp @@ -0,0 +1,23 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_HPP +#define JSONCONS_UBJSON_UBJSON_HPP + +#include +#include +#include +#include // std::enable_if +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +#endif diff --git a/third_party/jsoncons_ext/ubjson/ubjson_cursor.hpp b/third_party/jsoncons_ext/ubjson/ubjson_cursor.hpp new file mode 100644 index 0000000000..1ae08cf8f0 --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson_cursor.hpp @@ -0,0 +1,250 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_CURSOR_HPP +#define JSONCONS_UBJSON_UBJSON_CURSOR_HPP + +#include // std::allocator +#include +#include +#include +#include +#include +#include // std::basic_istream +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { +namespace ubjson { + +template > +class basic_ubjson_cursor : public basic_staj_cursor, private virtual ser_context +{ +public: + using source_type = Source; + using char_type = char; + using allocator_type = Allocator; +private: + basic_ubjson_parser parser_; + basic_staj_visitor cursor_visitor_; + bool eof_; + + // Noncopyable and nonmoveable + basic_ubjson_cursor(const basic_ubjson_cursor&) = delete; + basic_ubjson_cursor& operator=(const basic_ubjson_cursor&) = delete; + +public: + using string_view_type = string_view; + + template + basic_ubjson_cursor(Sourceable&& source, + const ubjson_decode_options& options = ubjson_decode_options(), + const Allocator& alloc = Allocator()) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + eof_(false) + { + if (!done()) + { + next(); + } + } + + // Constructors that set parse error codes + + template + basic_ubjson_cursor(Sourceable&& source, + std::error_code& ec) + : basic_ubjson_cursor(std::allocator_arg, Allocator(), + std::forward(source), + ubjson_decode_options(), + ec) + { + } + + template + basic_ubjson_cursor(Sourceable&& source, + const ubjson_decode_options& options, + std::error_code& ec) + : basic_ubjson_cursor(std::allocator_arg, Allocator(), + std::forward(source), + options, + ec) + { + } + + template + basic_ubjson_cursor(std::allocator_arg_t, const Allocator& alloc, + Sourceable&& source, + const ubjson_decode_options& options, + std::error_code& ec) + : parser_(std::forward(source), options, alloc), + cursor_visitor_(accept_all), + eof_(false) + { + if (!done()) + { + next(ec); + } + } + + void reset() + { + parser_.reset(); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + template + void reset(Sourceable&& source) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(); + } + } + + void reset(std::error_code& ec) + { + parser_.reset(); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + template + void reset(Sourceable&& source, std::error_code& ec) + { + parser_.reset(std::forward(source)); + cursor_visitor_.reset(); + eof_ = false; + if (!done()) + { + next(ec); + } + } + + bool done() const override + { + return parser_.done(); + } + + const staj_event& current() const override + { + return cursor_visitor_.event(); + } + + void read_to(basic_json_visitor& visitor) override + { + std::error_code ec; + read_to(visitor, ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void read_to(basic_json_visitor& visitor, + std::error_code& ec) override + { + if (cursor_visitor_.event().send_json_event(visitor, *this, ec)) + { + read_next(visitor, ec); + } + } + + void next() override + { + std::error_code ec; + next(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,parser_.line(),parser_.column())); + } + } + + void next(std::error_code& ec) override + { + read_next(ec); + } + + const ser_context& context() const override + { + return *this; + } + + bool eof() const + { + return eof_; + } + + std::size_t line() const override + { + return parser_.line(); + } + + std::size_t column() const override + { + return parser_.column(); + } + + friend + staj_filter_view operator|(basic_ubjson_cursor& cursor, + std::function pred) + { + return staj_filter_view(cursor, pred); + } + +private: + static bool accept_all(const staj_event&, const ser_context&) + { + return true; + } + + void read_next(std::error_code& ec) + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(cursor_visitor_, ec); + if (ec) return; + } + } + + void read_next(basic_json_visitor& visitor, std::error_code& ec) + { + parser_.restart(); + while (!parser_.stopped()) + { + parser_.parse(visitor, ec); + if (ec) return; + } + } +}; + +using ubjson_stream_cursor = basic_ubjson_cursor; +using ubjson_bytes_cursor = basic_ubjson_cursor; + +} // namespace ubjson +} // namespace jsoncons + +#endif + diff --git a/third_party/jsoncons_ext/ubjson/ubjson_encoder.hpp b/third_party/jsoncons_ext/ubjson/ubjson_encoder.hpp new file mode 100644 index 0000000000..b122038450 --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson_encoder.hpp @@ -0,0 +1,494 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_ENCODER_HPP +#define JSONCONS_UBJSON_UBJSON_ENCODER_HPP + +#include +#include +#include // std::numeric_limits +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace ubjson { + +enum class ubjson_container_type {object, indefinite_length_object, array, indefinite_length_array}; + +template > +class basic_ubjson_encoder final : public basic_json_visitor +{ + + enum class decimal_parse_state { start, integer, exp1, exp2, fraction1 }; +public: + using allocator_type = Allocator; + using typename basic_json_visitor::string_view_type; + using sink_type = Sink; + +private: + struct stack_item + { + ubjson_container_type type_; + std::size_t length_; + std::size_t count_; + + stack_item(ubjson_container_type type, std::size_t length = 0) noexcept + : type_(type), length_(length), count_(0) + { + } + + std::size_t length() const + { + return length_; + } + + std::size_t count() const + { + return count_; + } + + bool is_object() const + { + return type_ == ubjson_container_type::object || type_ == ubjson_container_type::indefinite_length_object; + } + + bool is_indefinite_length() const + { + return type_ == ubjson_container_type::indefinite_length_array || type_ == ubjson_container_type::indefinite_length_object; + } + + }; + + Sink sink_; + const ubjson_encode_options options_; + allocator_type alloc_; + + std::vector stack_; + int nesting_depth_; + + // Noncopyable and nonmoveable + basic_ubjson_encoder(const basic_ubjson_encoder&) = delete; + basic_ubjson_encoder& operator=(const basic_ubjson_encoder&) = delete; +public: + basic_ubjson_encoder(Sink&& sink, + const Allocator& alloc = Allocator()) + : basic_ubjson_encoder(std::forward(sink), ubjson_encode_options(), alloc) + { + } + + explicit basic_ubjson_encoder(Sink&& sink, + const ubjson_encode_options& options, + const Allocator& alloc = Allocator()) + : sink_(std::forward(sink)), + options_(options), + alloc_(alloc), + nesting_depth_(0) + { + } + + void reset() + { + stack_.clear(); + nesting_depth_ = 0; + } + + void reset(Sink&& sink) + { + sink_ = std::move(sink); + reset(); + } + + ~basic_ubjson_encoder() noexcept + { + JSONCONS_TRY + { + sink_.flush(); + } + JSONCONS_CATCH(...) + { + } + } + +private: + // Implementing methods + + void visit_flush() override + { + sink_.flush(); + } + + bool visit_begin_object(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = ubjson_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(ubjson_container_type::indefinite_length_object); + sink_.push_back(jsoncons::ubjson::ubjson_type::start_object_marker); + + return true; + } + + bool visit_begin_object(std::size_t length, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = ubjson_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(ubjson_container_type::object, length); + sink_.push_back(jsoncons::ubjson::ubjson_type::start_object_marker); + sink_.push_back(jsoncons::ubjson::ubjson_type::count_marker); + put_length(length); + + return true; + } + + bool visit_end_object(const ser_context&, std::error_code& ec) override + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + if (stack_.back().is_indefinite_length()) + { + sink_.push_back(jsoncons::ubjson::ubjson_type::end_object_marker); + } + else + { + if (stack_.back().count() < stack_.back().length()) + { + ec = ubjson_errc::too_few_items; + return false; + } + if (stack_.back().count() > stack_.back().length()) + { + ec = ubjson_errc::too_many_items; + return false; + } + } + stack_.pop_back(); + end_value(); + return true; + } + + bool visit_begin_array(semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = ubjson_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(ubjson_container_type::indefinite_length_array); + sink_.push_back(jsoncons::ubjson::ubjson_type::start_array_marker); + + return true; + } + + bool visit_begin_array(std::size_t length, semantic_tag, const ser_context&, std::error_code& ec) override + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = ubjson_errc::max_nesting_depth_exceeded; + return false; + } + stack_.emplace_back(ubjson_container_type::array, length); + sink_.push_back(jsoncons::ubjson::ubjson_type::start_array_marker); + sink_.push_back(jsoncons::ubjson::ubjson_type::count_marker); + put_length(length); + + return true; + } + + bool visit_end_array(const ser_context&, std::error_code& ec) override + { + JSONCONS_ASSERT(!stack_.empty()); + --nesting_depth_; + + if (stack_.back().is_indefinite_length()) + { + sink_.push_back(jsoncons::ubjson::ubjson_type::end_array_marker); + } + else + { + if (stack_.back().count() < stack_.back().length()) + { + ec = ubjson_errc::too_few_items; + return false; + } + if (stack_.back().count() > stack_.back().length()) + { + ec = ubjson_errc::too_many_items; + return false; + } + } + stack_.pop_back(); + end_value(); + return true; + } + + bool visit_key(const string_view_type& name, const ser_context&, std::error_code& ec) override + { + auto sink = unicode_traits::validate(name.data(), name.size()); + if (sink.ec != unicode_traits::conv_errc()) + { + ec = ubjson_errc::invalid_utf8_text_string; + return false; + } + + put_length(name.length()); + + for (auto c : name) + { + sink_.push_back(c); + } + return true; + } + + bool visit_null(semantic_tag, const ser_context&, std::error_code&) override + { + // nil + binary::native_to_big(static_cast(jsoncons::ubjson::ubjson_type::null_type), std::back_inserter(sink_)); + end_value(); + return true; + } + + bool visit_string(const string_view_type& sv, semantic_tag tag, const ser_context&, std::error_code& ec) override + { + switch (tag) + { + case semantic_tag::bigint: + case semantic_tag::bigdec: + { + sink_.push_back(jsoncons::ubjson::ubjson_type::high_precision_number_type); + break; + } + default: + { + sink_.push_back(jsoncons::ubjson::ubjson_type::string_type); + break; + } + } + + auto sink = unicode_traits::validate(sv.data(), sv.size()); + if (sink.ec != unicode_traits::conv_errc()) + { + ec = ubjson_errc::invalid_utf8_text_string; + return false; + } + + put_length(sv.length()); + + for (auto c : sv) + { + sink_.push_back(c); + } + + end_value(); + return true; + } + + void put_length(std::size_t length) + { + if (length <= (std::numeric_limits::max)()) + { + sink_.push_back(ubjson_type::uint8_type); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); + } + else if (length <= (std::size_t)(std::numeric_limits::max)()) + { + sink_.push_back(ubjson_type::int16_type); + binary::native_to_big(static_cast(length), std::back_inserter(sink_)); + } + else if (length <= (std::size_t)(std::numeric_limits::max)()) + { + sink_.push_back(ubjson_type::int32_type); + binary::native_to_big(static_cast(length),std::back_inserter(sink_)); + } + else if (length <= (std::size_t)(std::numeric_limits::max)()) + { + sink_.push_back(ubjson_type::int64_type); + binary::native_to_big(static_cast(length),std::back_inserter(sink_)); + } + else + { + JSONCONS_THROW(ser_error(ubjson_errc::too_many_items)); + } + } + + bool visit_byte_string(const byte_string_view& b, + semantic_tag, + const ser_context&, + std::error_code&) override + { + + const size_t length = b.size(); + sink_.push_back(jsoncons::ubjson::ubjson_type::start_array_marker); + sink_.push_back(static_cast(jsoncons::ubjson::ubjson_type::type_marker)); + sink_.push_back(static_cast(jsoncons::ubjson::ubjson_type::uint8_type)); + sink_.push_back(jsoncons::ubjson::ubjson_type::count_marker); + put_length(length); + + for (auto c : b) + { + sink_.push_back(c); + } + + end_value(); + return true; + } + + bool visit_double(double val, + semantic_tag, + const ser_context&, + std::error_code&) override + { + float valf = (float)val; + if ((double)valf == val) + { + // float 32 + sink_.push_back(static_cast(jsoncons::ubjson::ubjson_type::float32_type)); + binary::native_to_big(valf,std::back_inserter(sink_)); + } + else + { + // float 64 + sink_.push_back(static_cast(jsoncons::ubjson::ubjson_type::float64_type)); + binary::native_to_big(val,std::back_inserter(sink_)); + } + + // write double + + end_value(); + return true; + } + + bool visit_int64(int64_t val, + semantic_tag, + const ser_context&, + std::error_code&) override + { + if (val >= 0) + { + if (val <= (std::numeric_limits::max)()) + { + // uint 8 stores a 8-bit unsigned integer + sink_.push_back(jsoncons::ubjson::ubjson_type::uint8_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 16 stores a 16-bit big-endian unsigned integer + sink_.push_back(jsoncons::ubjson::ubjson_type::int16_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= (std::numeric_limits::max)()) + { + // uint 32 stores a 32-bit big-endian unsigned integer + sink_.push_back(jsoncons::ubjson::ubjson_type::int32_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= (std::numeric_limits::max)()) + { + // int 64 stores a 64-bit big-endian signed integer + sink_.push_back(jsoncons::ubjson::ubjson_type::int64_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else + { + // big integer + } + } + else + { + if (val >= (std::numeric_limits::lowest)()) + { + // int 8 stores a 8-bit signed integer + sink_.push_back(jsoncons::ubjson::ubjson_type::int8_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val >= (std::numeric_limits::lowest)()) + { + // int 16 stores a 16-bit big-endian signed integer + sink_.push_back(jsoncons::ubjson::ubjson_type::int16_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val >= (std::numeric_limits::lowest)()) + { + // int 32 stores a 32-bit big-endian signed integer + sink_.push_back(jsoncons::ubjson::ubjson_type::int32_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val >= (std::numeric_limits::lowest)()) + { + // int 64 stores a 64-bit big-endian signed integer + sink_.push_back(jsoncons::ubjson::ubjson_type::int64_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + } + end_value(); + return true; + } + + bool visit_uint64(uint64_t val, + semantic_tag, + const ser_context&, + std::error_code&) override + { + if (val <= (std::numeric_limits::max)()) + { + sink_.push_back(jsoncons::ubjson::ubjson_type::uint8_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= static_cast((std::numeric_limits::max)())) + { + sink_.push_back(jsoncons::ubjson::ubjson_type::int16_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= static_cast((std::numeric_limits::max)())) + { + sink_.push_back(jsoncons::ubjson::ubjson_type::int32_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + else if (val <= static_cast((std::numeric_limits::max)())) + { + sink_.push_back(jsoncons::ubjson::ubjson_type::int64_type); + binary::native_to_big(static_cast(val),std::back_inserter(sink_)); + } + end_value(); + return true; + } + + bool visit_bool(bool val, semantic_tag, const ser_context&, std::error_code&) override + { + // true and false + sink_.push_back(static_cast(val ? jsoncons::ubjson::ubjson_type::true_type : jsoncons::ubjson::ubjson_type::false_type)); + + end_value(); + return true; + } + + void end_value() + { + if (!stack_.empty()) + { + ++stack_.back().count_; + } + } +}; + +using ubjson_stream_encoder = basic_ubjson_encoder; +using ubjson_bytes_encoder = basic_ubjson_encoder>>; + +}} +#endif diff --git a/third_party/jsoncons_ext/ubjson/ubjson_error.hpp b/third_party/jsoncons_ext/ubjson/ubjson_error.hpp new file mode 100644 index 0000000000..2a414f8554 --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson_error.hpp @@ -0,0 +1,100 @@ +/// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_ERROR_HPP +#define JSONCONS_UBJSON_UBJSON_ERROR_HPP + +#include +#include + +namespace jsoncons { namespace ubjson { + +enum class ubjson_errc +{ + success = 0, + unexpected_eof = 1, + source_error, + count_required_after_type, + length_is_negative, + length_must_be_integer, + unknown_type, + invalid_utf8_text_string, + too_many_items, + too_few_items, + number_too_large, + max_nesting_depth_exceeded, + key_expected, + max_items_exceeded +}; + +class ubjson_error_category_impl + : public std::error_category +{ +public: + const char* name() const noexcept override + { + return "jsoncons/ubjson"; + } + std::string message(int ev) const override + { + switch (static_cast(ev)) + { + case ubjson_errc::unexpected_eof: + return "Unexpected end of file"; + case ubjson_errc::source_error: + return "Source error"; + case ubjson_errc::count_required_after_type: + return "Type is specified for container, but count is not specified"; + case ubjson_errc::length_is_negative: + return "Request for the length of an array, map or string returned a negative result"; + case ubjson_errc::length_must_be_integer: + return "Length must be a integer numeric type (int8, uint8, int16, int32, int64)"; + case ubjson_errc::unknown_type: + return "Unknown type"; + case ubjson_errc::invalid_utf8_text_string: + return "Illegal UTF-8 encoding in text string"; + case ubjson_errc::too_many_items: + return "Too many items were added to a UBJSON object or array of known length"; + case ubjson_errc::too_few_items: + return "Too few items were added to a UBJSON object or array of known length"; + case ubjson_errc::number_too_large: + return "Number exceeds implementation limits"; + case ubjson_errc::max_nesting_depth_exceeded: + return "Data item nesting exceeds limit in options"; + case ubjson_errc::key_expected: + return "Text string key in a map expected"; + case ubjson_errc::max_items_exceeded: + return "Number of items in UBJSON object or array exceeds limit set in options"; + default: + return "Unknown UBJSON parser error"; + } + } +}; + +inline +const std::error_category& ubjson_error_category() +{ + static ubjson_error_category_impl instance; + return instance; +} + +inline +std::error_code make_error_code(ubjson_errc e) +{ + return std::error_code(static_cast(e),ubjson_error_category()); +} + + +}} + +namespace std { + template<> + struct is_error_code_enum : public true_type + { + }; +} + +#endif diff --git a/third_party/jsoncons_ext/ubjson/ubjson_options.hpp b/third_party/jsoncons_ext/ubjson/ubjson_options.hpp new file mode 100644 index 0000000000..00302bc14b --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson_options.hpp @@ -0,0 +1,87 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_OPTIONS_HPP +#define JSONCONS_UBJSON_UBJSON_OPTIONS_HPP + +#include +#include // std::numeric_limits +#include +#include + +namespace jsoncons { namespace ubjson { + +class ubjson_options; + +class ubjson_options_common +{ + friend class ubjson_options; + + int max_nesting_depth_; +protected: + virtual ~ubjson_options_common() = default; + + ubjson_options_common() + : max_nesting_depth_(1024) + { + } + + ubjson_options_common(const ubjson_options_common&) = default; + ubjson_options_common& operator=(const ubjson_options_common&) = default; + ubjson_options_common(ubjson_options_common&&) = default; + ubjson_options_common& operator=(ubjson_options_common&&) = default; +public: + int max_nesting_depth() const + { + return max_nesting_depth_; + } +}; + +class ubjson_decode_options : public virtual ubjson_options_common +{ + friend class ubjson_options; + std::size_t max_items_; +public: + ubjson_decode_options() : + max_items_(1 << 24) + { + } + + std::size_t max_items() const + { + return max_items_; + } +}; + +class ubjson_encode_options : public virtual ubjson_options_common +{ + friend class ubjson_options; +public: + ubjson_encode_options() + { + } +}; + +class ubjson_options final : public ubjson_decode_options, public ubjson_encode_options +{ +public: + using ubjson_options_common::max_nesting_depth; + + ubjson_options& max_nesting_depth(int value) + { + this->max_nesting_depth_ = value; + return *this; + } + + ubjson_options& max_items(std::size_t value) + { + this->max_items_ = value; + return *this; + } +}; + +}} +#endif diff --git a/third_party/jsoncons_ext/ubjson/ubjson_parser.hpp b/third_party/jsoncons_ext/ubjson/ubjson_parser.hpp new file mode 100644 index 0000000000..5237e5c734 --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson_parser.hpp @@ -0,0 +1,880 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_PARSER_HPP +#define JSONCONS_UBJSON_UBJSON_PARSER_HPP + +#include +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace ubjson { + +enum class parse_mode {root,accept,array,indefinite_array,strongly_typed_array,map_key,map_value,strongly_typed_map_key,strongly_typed_map_value,indefinite_map_key,indefinite_map_value}; + +struct parse_state +{ + parse_mode mode; + std::size_t length; + uint8_t type; + std::size_t index; + + parse_state(parse_mode mode, std::size_t length, uint8_t type = 0) noexcept + : mode(mode), length(length), type(type), index(0) + { + } + + parse_state(const parse_state&) = default; + parse_state(parse_state&&) = default; +}; + +template > +class basic_ubjson_parser : public ser_context +{ + using char_type = char; + using char_traits_type = std::char_traits; + using temp_allocator_type = Allocator; + using char_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using byte_allocator_type = typename std::allocator_traits:: template rebind_alloc; + using parse_state_allocator_type = typename std::allocator_traits:: template rebind_alloc; + + Source source_; + ubjson_decode_options options_; + bool more_; + bool done_; + std::basic_string,char_allocator_type> text_buffer_; + std::vector state_stack_; + int nesting_depth_; +public: + template + basic_ubjson_parser(Sourceable&& source, + const ubjson_decode_options& options = ubjson_decode_options(), + const Allocator& alloc = Allocator()) + : source_(std::forward(source)), + options_(options), + more_(true), + done_(false), + text_buffer_(alloc), + state_stack_(alloc), + nesting_depth_(0) + { + state_stack_.emplace_back(parse_mode::root,0); + } + + void restart() + { + more_ = true; + } + + void reset() + { + more_ = true; + done_ = false; + text_buffer_.clear(); + state_stack_.clear(); + state_stack_.emplace_back(parse_mode::root,0,uint8_t(0)); + nesting_depth_ = 0; + } + + template + void reset(Sourceable&& source) + { + source_ = std::forward(source); + reset(); + } + + bool done() const + { + return done_; + } + + bool stopped() const + { + return !more_; + } + + std::size_t line() const override + { + return 0; + } + + std::size_t column() const override + { + return source_.position(); + } + + void parse(json_visitor& visitor, std::error_code& ec) + { + while (!done_ && more_) + { + switch (state_stack_.back().mode) + { + case parse_mode::array: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + read_type_and_value(visitor, ec); + if (ec) + { + return; + } + } + else + { + end_array(visitor, ec); + } + break; + } + case parse_mode::strongly_typed_array: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + read_value(visitor, state_stack_.back().type, ec); + if (ec) + { + return; + } + } + else + { + end_array(visitor, ec); + } + break; + } + case parse_mode::indefinite_array: + { + auto c = source_.peek(); + if (c.eof) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == jsoncons::ubjson::ubjson_type::end_array_marker) + { + source_.ignore(1); + end_array(visitor, ec); + if (ec) + { + return; + } + } + else + { + if (++state_stack_.back().index > options_.max_items()) + { + ec = ubjson_errc::max_items_exceeded; + more_ = false; + return; + } + read_type_and_value(visitor, ec); + if (ec) + { + return; + } + } + break; + } + case parse_mode::map_key: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + read_key(visitor, ec); + if (ec) + { + return; + } + state_stack_.back().mode = parse_mode::map_value; + } + else + { + end_object(visitor, ec); + } + break; + } + case parse_mode::map_value: + { + state_stack_.back().mode = parse_mode::map_key; + read_type_and_value(visitor, ec); + if (ec) + { + return; + } + break; + } + case parse_mode::strongly_typed_map_key: + { + if (state_stack_.back().index < state_stack_.back().length) + { + ++state_stack_.back().index; + read_key(visitor, ec); + if (ec) + { + return; + } + state_stack_.back().mode = parse_mode::strongly_typed_map_value; + } + else + { + end_object(visitor, ec); + } + break; + } + case parse_mode::strongly_typed_map_value: + { + state_stack_.back().mode = parse_mode::strongly_typed_map_key; + read_value(visitor, state_stack_.back().type, ec); + if (ec) + { + return; + } + break; + } + case parse_mode::indefinite_map_key: + { + auto c = source_.peek(); + if (c.eof) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == jsoncons::ubjson::ubjson_type::end_object_marker) + { + source_.ignore(1); + end_object(visitor, ec); + if (ec) + { + return; + } + } + else + { + if (++state_stack_.back().index > options_.max_items()) + { + ec = ubjson_errc::max_items_exceeded; + more_ = false; + return; + } + read_key(visitor, ec); + if (ec) + { + return; + } + state_stack_.back().mode = parse_mode::indefinite_map_value; + } + break; + } + case parse_mode::indefinite_map_value: + { + state_stack_.back().mode = parse_mode::indefinite_map_key; + read_type_and_value(visitor, ec); + if (ec) + { + return; + } + break; + } + case parse_mode::root: + { + state_stack_.back().mode = parse_mode::accept; + read_type_and_value(visitor, ec); + if (ec) + { + return; + } + break; + } + case parse_mode::accept: + { + JSONCONS_ASSERT(state_stack_.size() == 1); + state_stack_.clear(); + more_ = false; + done_ = true; + visitor.flush(); + break; + } + } + } + } +private: + void read_type_and_value(json_visitor& visitor, std::error_code& ec) + { + if (source_.is_error()) + { + ec = ubjson_errc::source_error; + more_ = false; + return; + } + + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + read_value(visitor, b, ec); + } + + void read_value(json_visitor& visitor, uint8_t type, std::error_code& ec) + { + switch (type) + { + case jsoncons::ubjson::ubjson_type::null_type: + { + more_ = visitor.null_value(semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::no_op_type: + { + break; + } + case jsoncons::ubjson::ubjson_type::true_type: + { + more_ = visitor.bool_value(true, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::false_type: + { + more_ = visitor.bool_value(false, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::int8_type: + { + uint8_t buf[sizeof(int8_t)]; + if (source_.read(buf, sizeof(int8_t)) != sizeof(int8_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + int8_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::uint8_type: + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + more_ = visitor.uint64_value(b, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::int16_type: + { + uint8_t buf[sizeof(int16_t)]; + if (source_.read(buf, sizeof(int16_t)) != sizeof(int16_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + int16_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::int32_type: + { + uint8_t buf[sizeof(int32_t)]; + if (source_.read(buf, sizeof(int32_t)) != sizeof(int32_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + int32_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::int64_type: + { + uint8_t buf[sizeof(int64_t)]; + if (source_.read(buf, sizeof(int64_t)) != sizeof(int64_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + int64_t val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.int64_value(val, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::float32_type: + { + uint8_t buf[sizeof(float)]; + if (source_.read(buf, sizeof(float)) != sizeof(float)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + float val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.double_value(val, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::float64_type: + { + uint8_t buf[sizeof(double)]; + if (source_.read(buf, sizeof(double)) != sizeof(double)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + double val = binary::big_to_native(buf, sizeof(buf)); + more_ = visitor.double_value(val, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::char_type: + { + text_buffer_.clear(); + if (source_reader::read(source_,text_buffer_,1) != 1) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + auto result = unicode_traits::validate(text_buffer_.data(),text_buffer_.size()); + if (result.ec != unicode_traits::conv_errc()) + { + ec = ubjson_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.string_value(text_buffer_, semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::string_type: + { + std::size_t length = get_length(ec); + if (ec) + { + return; + } + text_buffer_.clear(); + if (source_reader::read(source_,text_buffer_,length) != length) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + auto result = unicode_traits::validate(text_buffer_.data(),text_buffer_.size()); + if (result.ec != unicode_traits::conv_errc()) + { + ec = ubjson_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.string_value(jsoncons::basic_string_view(text_buffer_.data(),text_buffer_.length()), semantic_tag::none, *this, ec); + break; + } + case jsoncons::ubjson::ubjson_type::high_precision_number_type: + { + std::size_t length = get_length(ec); + if (ec) + { + return; + } + text_buffer_.clear(); + if (source_reader::read(source_,text_buffer_,length) != length) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (jsoncons::detail::is_base10(text_buffer_.data(),text_buffer_.length())) + { + more_ = visitor.string_value(jsoncons::basic_string_view(text_buffer_.data(),text_buffer_.length()), semantic_tag::bigint, *this, ec); + } + else + { + more_ = visitor.string_value(jsoncons::basic_string_view(text_buffer_.data(),text_buffer_.length()), semantic_tag::bigdec, *this, ec); + } + break; + } + case jsoncons::ubjson::ubjson_type::start_array_marker: + { + begin_array(visitor,ec); + break; + } + case jsoncons::ubjson::ubjson_type::start_object_marker: + { + begin_object(visitor, ec); + break; + } + default: + { + ec = ubjson_errc::unknown_type; + break; + } + } + if (ec) + { + more_ = false; + } + } + + void begin_array(json_visitor& visitor, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = ubjson_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + + auto c = source_.peek(); + if (c.eof) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == jsoncons::ubjson::ubjson_type::type_marker) + { + source_.ignore(1); + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + c = source_.peek(); + if (c.eof) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == jsoncons::ubjson::ubjson_type::count_marker) + { + source_.ignore(1); + std::size_t length = get_length(ec); + if (ec) + { + return; + } + if (length > options_.max_items()) + { + ec = ubjson_errc::max_items_exceeded; + more_ = false; + return; + } + state_stack_.emplace_back(parse_mode::strongly_typed_array,length,b); + more_ = visitor.begin_array(length, semantic_tag::none, *this, ec); + } + else + { + ec = ubjson_errc::count_required_after_type; + more_ = false; + return; + } + } + else if (c.value == jsoncons::ubjson::ubjson_type::count_marker) + { + source_.ignore(1); + std::size_t length = get_length(ec); + if (ec) + { + return; + } + if (length > options_.max_items()) + { + ec = ubjson_errc::max_items_exceeded; + more_ = false; + return; + } + state_stack_.emplace_back(parse_mode::array,length); + more_ = visitor.begin_array(length, semantic_tag::none, *this, ec); + } + else + { + state_stack_.emplace_back(parse_mode::indefinite_array,0); + more_ = visitor.begin_array(semantic_tag::none, *this, ec); + } + } + + void end_array(json_visitor& visitor, std::error_code& ec) + { + --nesting_depth_; + + more_ = visitor.end_array(*this, ec); + state_stack_.pop_back(); + } + + void begin_object(json_visitor& visitor, std::error_code& ec) + { + if (JSONCONS_UNLIKELY(++nesting_depth_ > options_.max_nesting_depth())) + { + ec = ubjson_errc::max_nesting_depth_exceeded; + more_ = false; + return; + } + + auto c = source_.peek(); + if (c.eof) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == jsoncons::ubjson::ubjson_type::type_marker) + { + source_.ignore(1); + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + c = source_.peek(); + if (c.eof) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == jsoncons::ubjson::ubjson_type::count_marker) + { + source_.ignore(1); + std::size_t length = get_length(ec); + if (ec) + { + return; + } + if (length > options_.max_items()) + { + ec = ubjson_errc::max_items_exceeded; + more_ = false; + return; + } + state_stack_.emplace_back(parse_mode::strongly_typed_map_key,length,b); + more_ = visitor.begin_object(length, semantic_tag::none, *this, ec); + } + else + { + ec = ubjson_errc::count_required_after_type; + more_ = false; + return; + } + } + else + { + c = source_.peek(); + if (c.eof) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + if (c.value == jsoncons::ubjson::ubjson_type::count_marker) + { + source_.ignore(1); + std::size_t length = get_length(ec); + if (ec) + { + return; + } + if (length > options_.max_items()) + { + ec = ubjson_errc::max_items_exceeded; + more_ = false; + return; + } + state_stack_.emplace_back(parse_mode::map_key,length); + more_ = visitor.begin_object(length, semantic_tag::none, *this, ec); + } + else + { + state_stack_.emplace_back(parse_mode::indefinite_map_key,0); + more_ = visitor.begin_object(semantic_tag::none, *this, ec); + } + } + } + + void end_object(json_visitor& visitor, std::error_code& ec) + { + --nesting_depth_; + more_ = visitor.end_object(*this, ec); + state_stack_.pop_back(); + } + + std::size_t get_length(std::error_code& ec) + { + std::size_t length = 0; + uint8_t type; + if (source_.read(&type, 1) == 0) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return length; + } + switch (type) + { + case jsoncons::ubjson::ubjson_type::int8_type: + { + uint8_t buf[sizeof(int8_t)]; + if (source_.read(buf, sizeof(int8_t)) != sizeof(int8_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return length; + } + int8_t val = binary::big_to_native(buf, sizeof(buf)); + if (val >= 0) + { + length = val; + } + else + { + ec = ubjson_errc::length_is_negative; + more_ = false; + return length; + } + break; + } + case jsoncons::ubjson::ubjson_type::uint8_type: + { + uint8_t b; + if (source_.read(&b, 1) == 0) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return length; + } + length = b; + break; + } + case jsoncons::ubjson::ubjson_type::int16_type: + { + uint8_t buf[sizeof(int16_t)]; + if (source_.read(buf, sizeof(int16_t)) != sizeof(int16_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return length; + } + int16_t val = binary::big_to_native(buf, sizeof(buf)); + if (val >= 0) + { + length = val; + } + else + { + ec = ubjson_errc::length_is_negative; + more_ = false; + return length; + } + break; + } + case jsoncons::ubjson::ubjson_type::int32_type: + { + uint8_t buf[sizeof(int32_t)]; + if (source_.read(buf, sizeof(int32_t)) != sizeof(int32_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return length; + } + int32_t val = binary::big_to_native(buf, sizeof(buf)); + if (val >= 0) + { + length = static_cast(val); + } + else + { + ec = ubjson_errc::length_is_negative; + more_ = false; + return length; + } + break; + } + case jsoncons::ubjson::ubjson_type::int64_type: + { + uint8_t buf[sizeof(int64_t)]; + if (source_.read(buf, sizeof(int64_t)) != sizeof(int64_t)) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return length; + } + int64_t val = binary::big_to_native(buf, sizeof(buf)); + if (val >= 0) + { + length = (std::size_t)val; + if (length != (uint64_t)val) + { + ec = ubjson_errc::number_too_large; + more_ = false; + return length; + } + } + else + { + ec = ubjson_errc::length_is_negative; + more_ = false; + return length; + } + break; + } + default: + { + ec = ubjson_errc::length_must_be_integer; + more_ = false; + return length; + } + } + return length; + } + + void read_key(json_visitor& visitor, std::error_code& ec) + { + std::size_t length = get_length(ec); + if (ec) + { + ec = ubjson_errc::key_expected; + more_ = false; + return; + } + text_buffer_.clear(); + if (source_reader::read(source_,text_buffer_,length) != length) + { + ec = ubjson_errc::unexpected_eof; + more_ = false; + return; + } + + auto result = unicode_traits::validate(text_buffer_.data(),text_buffer_.size()); + if (result.ec != unicode_traits::conv_errc()) + { + ec = ubjson_errc::invalid_utf8_text_string; + more_ = false; + return; + } + more_ = visitor.key(jsoncons::basic_string_view(text_buffer_.data(),text_buffer_.length()), *this, ec); + } +}; + +}} + +#endif diff --git a/third_party/jsoncons_ext/ubjson/ubjson_reader.hpp b/third_party/jsoncons_ext/ubjson/ubjson_reader.hpp new file mode 100644 index 0000000000..a2509857a4 --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson_reader.hpp @@ -0,0 +1,87 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_READER_HPP +#define JSONCONS_UBJSON_UBJSON_READER_HPP + +#include +#include +#include // std::move +#include +#include +#include +#include +#include +#include +#include + +namespace jsoncons { namespace ubjson { + +template > +class basic_ubjson_reader +{ + basic_ubjson_parser parser_; + json_visitor& visitor_; +public: + template + basic_ubjson_reader(Sourceable&& source, + json_visitor& visitor, + const Allocator& alloc) + : basic_ubjson_reader(std::forward(source), + visitor, + ubjson_decode_options(), + alloc) + { + } + + template + basic_ubjson_reader(Sourceable&& source, + json_visitor& visitor, + const ubjson_decode_options& options = ubjson_decode_options(), + const Allocator& alloc=Allocator()) + : parser_(std::forward(source), options, alloc), + visitor_(visitor) + { + } + + void read() + { + std::error_code ec; + read(ec); + if (ec) + { + JSONCONS_THROW(ser_error(ec,line(),column())); + } + } + + void read(std::error_code& ec) + { + parser_.reset(); + parser_.parse(visitor_, ec); + if (ec) + { + return; + } + } + + std::size_t line() const + { + return parser_.line(); + } + + std::size_t column() const + { + return parser_.column(); + } +}; + +using ubjson_stream_reader = basic_ubjson_reader; + +using ubjson_bytes_reader = basic_ubjson_reader; + +}} + +#endif diff --git a/third_party/jsoncons_ext/ubjson/ubjson_type.hpp b/third_party/jsoncons_ext/ubjson/ubjson_type.hpp new file mode 100644 index 0000000000..4adf3f53f8 --- /dev/null +++ b/third_party/jsoncons_ext/ubjson/ubjson_type.hpp @@ -0,0 +1,43 @@ +// Copyright 2013-2024 Daniel Parker +// Distributed under the Boost license, Version 1.0. +// (See accompanying file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +// See https://github.com/danielaparker/jsoncons for latest version + +#ifndef JSONCONS_UBJSON_UBJSON_TYPE_HPP +#define JSONCONS_UBJSON_UBJSON_TYPE_HPP + +#include +#include +#include + +namespace jsoncons { namespace ubjson { + + namespace ubjson_type + { + const uint8_t null_type = 'Z'; + const uint8_t no_op_type = 'N'; + const uint8_t true_type = 'T'; + const uint8_t false_type = 'F'; + const uint8_t int8_type = 'i'; + const uint8_t uint8_type = 'U'; + const uint8_t int16_type = 'I'; + const uint8_t int32_type = 'l'; + const uint8_t int64_type = 'L'; + const uint8_t float32_type = 'd'; + const uint8_t float64_type = 'D'; + const uint8_t high_precision_number_type = 'H'; + const uint8_t char_type = 'C'; + const uint8_t string_type = 'S'; + const uint8_t start_array_marker = '['; + const uint8_t end_array_marker = ']'; + const uint8_t start_object_marker = '{'; + const uint8_t end_object_marker = '}'; + const uint8_t type_marker = '$'; + const uint8_t count_marker = '#'; + } + +} // namespace ubjson +} // namespace jsoncons + +#endif diff --git a/version.json.dist.in b/version.json.dist.in index b051111505..74470d0bf1 100644 --- a/version.json.dist.in +++ b/version.json.dist.in @@ -1,7 +1,7 @@ { "schema_name": "version", - "schema_version": "v4", + "schema_version": "v@IRODS_CONFIGURATION_SCHEMA_VERSION@", "irods_version": "@IRODS_VERSION@", - "catalog_schema_version": 11, + "catalog_schema_version": @IRODS_CATALOG_SCHEMA_VERSION@, "commit_id": "@IRODS_GIT_SHA1@" }