diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..470eb61 --- /dev/null +++ b/.gitignore @@ -0,0 +1,169 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.userprefs +*.user + +# Build results +output +msvc/vs11/Debug +msvc/vs11/Release +msvc/vs12/Debug +msvc/vs12/Release + +# CMake intermediate files +CMakeCache.txt +Testing/ +CMakeFiles/ +*CMakeScripts/ +*.cmake +!cmake/modules/*.cmake +Makefile +*odatacpp.build/ +odatacpp.xcodeproj/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +*_i.c +*_p.c +*.ilk +*.meta +*.dylib +*.obj +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.log +*.scc + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET codgit ing add-in +_ReSharper*/ +*.[Rr]e[Ss]harper + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +*.ncrunch* +.*crunch*.local.xml + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.Publish.xml + +# Windows Azure Build Output +csx +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Others +sql/ +*.Cache +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.[Pp]ublish.xml +*.pfx +*.publishsettings + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file to a newer +# Visual Studio version. Backup files are not needed, because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +App_Data/*.mdf +App_Data/*.ldf + +#LightSwitch generated files +GeneratedArtifacts/ +_Pvt_Extensions/ +ModelManifest.xml + +# ========================= +# Windows detritus +# ========================= + +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Mac desktop service store files +.DS_Store + +# OSX +xcuserdata +*.o +*.d +test_runner +*.dsym +*.xccheckout +*.so + +# Visual studio +*.i diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..f3a92ae --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +set(CMAKE_LEGACY_CYGWIN_WIN32 0) +cmake_minimum_required(VERSION 2.6) +project(odatacpp-server) + +enable_testing() + +set(WARNINGS) + +# Platform (not compiler) specific settings +if(UNIX) # This includes OSX + find_package(Boost REQUIRED COMPONENTS locale filesystem system) + find_package(LibXml2 REQUIRED) + + option(BUILD_SHARED_LIBS "Build shared Libraries." ON) +else() + message(FATAL_ERROR "-- Unsupported Build Platform.") +endif() + +# Compiler (not platform) specific settings +if(("${CMAKE_CXX_COMPILER_ID}" MATCHES "Clang")) + message("-- Setting clang options") + + set(WARNINGS "-Wall -Wextra -Wcast-qual -Wformat=2 -Winit-self -Winvalid-pch -Wmissing-format-attribute -Wmissing-include-dirs -Wpacked -Wredundant-decls") + set(OSX_SUPPRESSIONS "-Wno-overloaded-virtual -Wno-sign-conversion -Wno-deprecated -Wno-unknown-pragmas -Wno-reorder -Wno-char-subscripts -Wno-switch -Wno-unused-parameter -Wno-unused-variable -Wno-deprecated -Wno-unused-value -Wno-unknown-warning-option -Wno-return-type-c-linkage -Wno-unused-function -Wno-sign-compare -Wno-shorten-64-to-32 -Wno-reorder -Wno-ignored-qualifiers -Wno-sometimes-uninitialized -Wno-logical-op-parentheses -Wno-unused-private-field") + set(WARNINGS "${WARNINGS} ${OSX_SUPPRESSIONS}") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libc++ -Wno-return-type-c-linkage -Wno-unneeded-internal-declaration") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LIBRARY "libc++") + set(CMAKE_XCODE_ATTRIBUTE_CLANG_CXX_LANGUAGE_STANDARD "c++11") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-strict-aliasing") + set(STRICT_CXX_FLAGS ${WARNINGS} "-Werror -pedantic") +elseif("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") + message("-- Setting gcc options") + + set(WARNINGS "-Wall -Wextra -Wcast-align -Wformat=2 -Winit-self -Winvalid-pch -Wmissing-format-attribute -Wmissing-include-dirs -Wpacked -Wredundant-decls -Wunreachable-code") + set(LINUX_SUPPRESSIONS "-Wno-deprecated -Wno-unknown-pragmas -Wno-reorder -Wno-unused-function -Wno-char-subscripts -Wno-switch -Wno-unused-but-set-parameter -Wno-unused-value -Wno-unused-local-typedefs -Wno-sign-compare -Wno-unused-parameter -Wno-unused-variable -Wno-ignored-qualifiers -Wno-cast-qual -Wno-parentheses -Wno-return-type") + + set(WARNINGS "${WARNINGS} ${LINUX_SUPPRESSIONS}") + set(LD_FLAGS "${LD_FLAGS} -Wl,-z,defs") + + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11 -fno-strict-aliasing") + set(STRICT_CXX_FLAGS ${WARNINGS} "-Werror -pedantic") +else() + message("-- Unknown compiler, success is doubtful.") +endif() + +# Reconfigure final output directory +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/output) +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/output) +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${PROJECT_BINARY_DIR}/output) + +# These settings can be used by the test targets +set(ODATACPP_LIB_DIR ${CMAKE_CURRENT_SOURCE_DIR}/lib) +set(ODATACPP_INCLUDE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/include) +set(ODATACPP_INCLUDE_DIRS ${ODATACPP_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${LIBXML2_INCLUDE_DIR}) +set(ODATACPP_TEST_DIR ${CMAKE_CURRENT_SOURCE_DIR}/tests) + +# OData library targets +set(ODATACPP_LIBRARY ${LIB}odata-library) + +# Everything in the project needs access to the odatacpp include directories +include_directories(${ODATACPP_INCLUDE_DIRS}) + +add_subdirectory(src) +add_subdirectory(tests) diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..42aeb39 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,9 @@ +ODataCpp Server ver. 1.0.0 +Copyright (c) Microsoft Corporation +All rights reserved. +MIT License +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. diff --git a/README.md b/README.md new file mode 100644 index 0000000..035e273 --- /dev/null +++ b/README.md @@ -0,0 +1,130 @@ +# Welcome to ODataCpp-Server +ODataCpp is an open-source C++ library that implements the Open Data Protocol (OData). It supports the [OData protocol version 4.0](http://docs.oasis-open.org/odata/odata/v4.0/os/part1-protocol/odata-v4.0-os-part1-protocol.html). This is the server library that helps you build OData V4 service with C++. + +# Getting started + +## Getting the source + + git clone https://github.com/odata/odatacpp-server + +## Building and testing + +Currently the following target platforms are supported: + + * Windows 32-bit + * OS X + * Linux + +### Building on Windows with Visual Studio 2012/2013 + +1.Please ensure that you have Visual Studio 2012/2013 installed. + +2.Open 'odatacpp.sln' under 'odatacpp-server\msvc' with VS2012/VS2013 and click 'Build Solution' in the 'BUILD' menu. + +3.Built libraries are placed under 'odatacpp-server\output\\\' where '\' could be either 'Debug' or 'Release' according to your build configuration in VS2012/VS2013. + +### Building on Windows with MSBuild + +1.Setup build environment for VS2012/VS2013: + + cd odatacpp-server + powershell + .\setup_ps_env_VS2012.ps1 (or .\setup_ps_env_VS2013.ps1) + + If you receive an error message like 'running scripts is disabled on this system', please run PowerShell as administrator, type the following command, and then rerun the setup script above. + + Set-ExecutionPolicy RemoteSigned + +2.Here are some examples to invoke MSBuild: + + 1) Build Debug version of ODataCpp-Server libraries in parallel. + + msbuild /m + + 2) Build Release version of ODataCpp-Server libraries. + + msbuild /p:Configuration=Release + + 3) Rebuild Debug version of ODataCpp-Server. + + msbuild /t:Rebuild /p:Configuration=Debug + + 4) Clean build outputs. + + msbuild /t:Clean + +3.Built libraries are placed under the same folder of VS2012/VS2013. Actually they are no different than the ones built by VS2012/VS2013. + +### Running tests on Windows + +1.After you have successfully built the libraries, you can run our functional tests to check the basic functionality. + + 1) Test the Debug version of ODataCpp-Server Libraries: + + cd odatacpp-server\output\Debug + TestRunner.exe odata_functional_test.vs11d.dll /Desktop + + 2) Test the Release version of ODataCpp-Server Libraries: + + cd odatacpp\output\Release + TestRunner.exe odata_functional_test.vs11.dll /Desktop + + The '/Desktop' option here indicates to run tests for desktop (rather than rt). + +### Building on OS X + +1.Please ensure that you have OS X later than 10.9, Xcode later than 5.0 and Xcode Command Line Tools installed. + +2.Install the Homebrew package manager (http://brew.sh). Skip this step if you want to use your own package manager. + +3.Install the required packages to build ODataCpp-Server via Homebrew or your own package manager. + + brew install cmake boost + +4.Return to the root folder of ODataCpp-Server and generate 'Makefile' using CMake. + + cmake -DCMAKE_BUILD_TYPE=Debug # replace 'Debug' with 'Release' if needed + make + +5.Please find your built libraries under 'output'. + +### Running tests on OS X + +After successfully building the libraries, you can run the functional and end-to-end tests via the terminal. + + cd odatacpp-server/output + ./test_runner *tests* + +### Building on Linux + +1.This document is based on Ubuntu 14.04 LTS 64-bit. Process on other Linux distributions should be similar. + +2.Install the required packages to build ODataCpp-Server via apt-get or your own package manager. + + sudo apt-get install cmake libxml2 libxml2-dev libboost1.55-dev libboost-system1.55.0 libboost-system1.55-dev libboost-locale1.55.0 libboost-locale1.55-dev libboost-filesystem1.55.0 libboost-filesystem1.55-dev + + You can choose other versions of boost to install but they are not guaranteed to work. + +3.Return to the root folder of ODataCpp-Server and generate 'Makefile' using CMake. + + cmake -DCMAKE_BUILD_TYPE=Debug # replace 'Debug' with 'Release' if needed + make # don't build in parallel because gcc will be very likely to crash + +4.Please find your built libraries under 'output'. + +### Running tests on Linux + +After successfully building the libraries, you can run the functional and end-to-end tests via the terminal. + + cd odatacpp-server/output + ./test_runner *tests* + +# Documentation +Please refer to our [GitHub pages](https://odata.github.io/odatacpp-server) for documentation. + +# Community +## Issue tracker +To report bugs and require features, please use our [issue tracker](https://github.com/odata/odatacpp-server/issues?state=open). + +## Team blog +Please visit http://blogs.msdn.com/b/odatateam/. diff --git a/build.root b/build.root new file mode 100644 index 0000000..d423a1a --- /dev/null +++ b/build.root @@ -0,0 +1 @@ +Marker file indicating root of build system. diff --git a/dirs.proj b/dirs.proj new file mode 100644 index 0000000..409f521 --- /dev/null +++ b/dirs.proj @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/include/odata/common/asyncrt_utils.h b/include/odata/common/asyncrt_utils.h new file mode 100644 index 0000000..6f7c515 --- /dev/null +++ b/include/odata/common/asyncrt_utils.h @@ -0,0 +1,425 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/xxpublic.h" +#include "odata/common/basic_types.h" +#include +#include +#include +#include +#include + +#if !defined(_MS_WINDOWS) || (_MSC_VER >= 1700) +#include +#endif + +#ifndef _MS_WINDOWS +#include +#endif + +namespace odata { namespace utility +{ + +#if !defined(_MS_WINDOWS) || (_MSC_VER >= 1700) // Post VS10 and Linux +typedef std::chrono::seconds seconds; +#else // VS10 +/// +/// A type used to represent timeouts for Azure storage APIs +/// +// The chrono header is not present on Visual Studio with versions < 1700 so we define a 'seconds' type for timeouts +class seconds +{ +public: + explicit seconds(__int64 time = 0): m_count(time) {} + __int64 count() const { return m_count; } + +private: + __int64 m_count; +}; +#endif + +namespace timespan +{ + /// + /// Converts a timespan/interval in seconds to xml duration string as specified by + /// http://www.w3.org/TR/xmlschema-2/#duration + /// + ODATACPP_API utility::string_t __cdecl seconds_to_xml_duration(utility::seconds numSecs); + + /// + /// Converts an xml duration to timespan/interval in seconds + /// http://www.w3.org/TR/xmlschema-2/#duration + /// + ODATACPP_API utility::seconds __cdecl xml_duration_to_seconds(utility::string_t timespanString); +} + +namespace conversions +{ + /// + /// Converts a UTF-16 string to a UTF-8 string + /// + std::string __cdecl utf16_to_utf8(const utf16string &w); + + /// + /// Converts a UTF-8 string to a UTF-16 + /// + ODATACPP_API utf16string __cdecl utf8_to_utf16(const std::string &s); + + /// + /// Converts a ASCII (us-ascii) string to a UTF-16 string. + /// + ODATACPP_API utf16string __cdecl usascii_to_utf16(const std::string &s); + + /// + /// Converts a Latin1 (iso-8859-1) string to a UTF-16 string. + /// + ODATACPP_API utf16string __cdecl latin1_to_utf16(const std::string &s); + + /// + /// Converts a string with the OS's default code page to a UTF-16 string. + /// + ODATACPP_API utf16string __cdecl default_code_page_to_utf16(const std::string &s); + + /// + /// Decode to string_t from either a utf-16 or utf-8 string + /// + ODATACPP_API utility::string_t __cdecl to_string_t(std::string &&s); + ODATACPP_API utility::string_t __cdecl to_string_t(utf16string &&s); + ODATACPP_API utility::string_t __cdecl to_string_t(const std::string &s); + ODATACPP_API utility::string_t __cdecl to_string_t(const utf16string &s); + + /// + /// Decode to utf16 from either a narrow or wide string + /// + ODATACPP_API utf16string __cdecl to_utf16string(const std::string &value); + ODATACPP_API utf16string __cdecl to_utf16string(utf16string value); + + /// + /// Decode to UTF-8 from either a narrow or wide string. + /// + ODATACPP_API std::string __cdecl to_utf8string(std::string value); + ODATACPP_API std::string __cdecl to_utf8string(const utf16string &value); + + /// + /// Encode the given byte array into a base64 string + /// + ODATACPP_API utility::string_t __cdecl to_base64(const std::vector& data); + + /// + /// Encode the given 8-byte integer into a base64 string + /// + ODATACPP_API utility::string_t __cdecl to_base64(uint64_t data); + + /// + /// Decode the given base64 string to a byte array + /// + ODATACPP_API std::vector __cdecl from_base64(const utility::string_t& str); + + template + utility::string_t print_string(const Source &val) + { + utility::ostringstream_t oss; + oss << val; + if (oss.bad()) + throw std::bad_cast(); + return oss.str(); + } + template + Target scan_string(const utility::string_t &str) + { + Target t; + utility::istringstream_t iss(str); + iss >> t; + if (iss.bad()) + throw std::bad_cast(); + return t; + } +} + +namespace details +{ + /// + /// Our own implementation of alpha numeric instead of std::isalnum to avoid + /// taking global lock for performance reasons. + /// + inline bool __cdecl is_alnum(char ch) + { + return (ch >= '0' && ch <= '9') + || (ch >= 'A' && ch <= 'Z') + || (ch >= 'a' && ch <= 'z'); + } + + /// + /// Simplistic implementation of make_unique. A better implementation would be based on variadic templates + /// and therefore not be compatible with Dev10. + /// + template + std::unique_ptr<_Type> make_unique() { + return std::unique_ptr<_Type>(new _Type()); + } + + template + std::unique_ptr<_Type> make_unique(_Arg1&& arg1) { + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1))); + } + + template + std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2) { + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2))); + } + + template + std::unique_ptr<_Type> make_unique(_Arg1&& arg1, _Arg2&& arg2, _Arg3&& arg3) { + return std::unique_ptr<_Type>(new _Type(std::forward<_Arg1>(arg1), std::forward<_Arg2>(arg2), std::forward<_Arg3>(arg3))); + } + + /// + /// Cross platform utility function for performing case insensitive string comparision. + /// + /// First string to compare. + /// Second strong to compare. + /// true if the strings are equivalent, false otherwise + inline bool str_icmp(const utility::string_t &left, const utility::string_t &right) + { +#ifdef _MS_WINDOWS + return _wcsicmp(left.c_str(), right.c_str()) == 0; +#else + return boost::iequals(left, right); +#endif + } + + // Turn const_iterator into an iterator + template + typename Container::iterator remove_iterator_constness(Container& c, ConstIterator it) + { + return c.erase(it, it); + } + +#ifdef _MS_WINDOWS + +/// +/// Category error type for Windows OS errors. +/// +class windows_category_impl : public std::error_category +{ +public: + virtual const char *name() const { return "windows"; } + + ODATACPP_API virtual std::string message(int errorCode) const; + + ODATACPP_API virtual std::error_condition default_error_condition(int errorCode) const; +}; + +/// +/// Gets the one global instance of the windows error category. +/// +/// An error category instance. +ODATACPP_API const std::error_category & __cdecl windows_category(); + +#else + +/// +/// Gets the one global instance of the linux error category. +/// +/// An error category instance. +ODATACPP_API const std::error_category & __cdecl linux_category(); + +#endif + +/// +/// Gets the one global instance of the current platform's error category. +/// +ODATACPP_API const std::error_category & __cdecl platform_category(); + +/// +/// Creates an instance of std::system_error from a OS error code. +/// +inline std::system_error __cdecl create_system_error(unsigned long errorCode) +{ + std::error_code code((int)errorCode, platform_category()); + return std::system_error(code, code.message()); +} + +/// +/// Creates a std::error_code from a OS error code. +/// +inline std::error_code __cdecl create_error_code(unsigned long errorCode) +{ + return std::error_code((int)errorCode, platform_category()); +} + +/// +/// Creates the corresponding error message from a OS error code. +/// +inline utility::string_t __cdecl create_error_message(unsigned long errorCode) +{ + return utility::conversions::to_string_t(create_error_code(errorCode).message()); +} + +} + +class datetime +{ +public: + typedef uint64_t interval_type; + + /// + /// Defines the supported date and time string formats. + /// + enum date_format { RFC_1123, ISO_8601 }; + + /// + /// Returns the current UTC time. + /// + static ODATACPP_API datetime __cdecl utc_now(); + + datetime() : m_interval(0) + { + } + + /// + /// Creates datetime from a string representing time in UTC in RFC 1123 format. + /// + static ODATACPP_API datetime __cdecl from_string(const utility::string_t& timestring, date_format format = RFC_1123); + + /// + /// Returns a string representation of the datetime. + /// + ODATACPP_API utility::string_t to_string(date_format format = RFC_1123) const; + + /// + /// Returns the integral time value. + /// + interval_type to_interval() const + { + return m_interval; + } + + datetime operator- (interval_type value) const + { + return datetime(m_interval - value); + } + + datetime operator+ (interval_type value) const + { + return datetime(m_interval + value); + } + + bool operator== (datetime dt) const + { + return m_interval == dt.m_interval; + } + + bool operator!= (const datetime& dt) const + { + return !(*this == dt); + } + + static interval_type from_milliseconds(unsigned int milliseconds) + { + return milliseconds*_msTicks; + } + + static interval_type from_seconds(unsigned int seconds) + { + return seconds*_secondTicks; + } + + static interval_type from_minutes(unsigned int minutes) + { + return minutes*_minuteTicks; + } + + static interval_type from_hours(unsigned int hours) + { + return hours*_hourTicks; + } + + static interval_type from_days(unsigned int days) + { + return days*_dayTicks; + } + + bool is_initialized() const + { + return m_interval != 0; + } + +private: + + friend int operator- (datetime t1, datetime t2); + + static const interval_type _msTicks = static_cast(10000); + static const interval_type _secondTicks = 1000*_msTicks; + static const interval_type _minuteTicks = 60*_secondTicks; + static const interval_type _hourTicks = 60*60*_secondTicks; + static const interval_type _dayTicks = 24*60*60*_secondTicks; + + +#ifdef _MS_WINDOWS + // void* to avoid pulling in windows.h + static ODATACPP_API bool __cdecl datetime::system_type_to_datetime(/*SYSTEMTIME*/ void* psysTime, uint64_t seconds, datetime * pdt); +#else + static datetime timeval_to_datetime(struct timeval time); +#endif + + // Private constructor. Use static methods to create an instance. + datetime(interval_type interval) : m_interval(interval) + { + } + + interval_type m_interval; +}; + +#ifndef _MS_WINDOWS + +// temporary workaround for the fact that +// utf16char is not fully supported in GCC +class cmp +{ +public: + + static int icmp(std::string left, std::string right) + { + size_t i; + for (i = 0; i < left.size(); ++i) + { + if (i == right.size()) return 1; + + auto l = cmp::tolower(left[i]); + auto r = cmp::tolower(right[i]); + if (l > r) return 1; + if (l < r) return -1; + } + if (i < right.size()) return -1; + return 0; + } + +private: + static char tolower(char c) + { + if (c >= 'A' && c <= 'Z') + return static_cast(c - 'A' + 'a'); + return c; + } +}; + +#endif + +inline int operator- (datetime t1, datetime t2) +{ + auto diff = (t1.m_interval - t2.m_interval); + + // Round it down to seconds + diff /= 10 * 1000 * 1000; + + return static_cast(diff); +} + +} // namespace utility; +} // namespace odata; diff --git a/include/odata/common/base_uri.h b/include/odata/common/base_uri.h new file mode 100644 index 0000000..4e6250e --- /dev/null +++ b/include/odata/common/base_uri.h @@ -0,0 +1,373 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include +#include + +#include "odata/common/asyncrt_utils.h" +#include "odata/common/xxpublic.h" +#include "odata/common/basic_types.h" + +namespace odata { namespace utility { + namespace details + { + struct _uri_components + { + _uri_components() + { + m_path = _XPLATSTR("/"); + m_port = -1; + } + + ODATACPP_API utility::string_t join(); + + utility::string_t m_scheme; + utility::string_t m_host; + utility::string_t m_user_info; + utility::string_t m_path; + utility::string_t m_query; + utility::string_t m_fragment; + int m_port; + }; + } + + /// + /// A single exception type to represent errors in parsing, encoding, and decoding URIs. + /// + class uri_exception : public std::exception + { + public: + + uri_exception(std::string msg) : m_msg(std::move(msg)) {} + + ~uri_exception() _noexcept {} + + const char* what() const _noexcept + { + return m_msg.c_str(); + } + + private: + std::string m_msg; + }; + + /// + /// A flexible, protocol independent uri implementation. + /// + /// URI instances are immutable. Querying the various fields on an emtpy uri will return empty strings. Querying + /// various diagnostic members on an empty uri will return false. + /// + /// + /// This implementation accepts both uris ('http://msn.com/path') and uri relative-references + /// ('/path?query#frag'). + /// + /// This implementation does not provide any scheme-specific handling -- an example of this + /// would be the following: 'http://path1/path'. This is a valid uri, but it's not a valid + /// http-uri -- that is, it's syntactically correct but does not conform to the requirements + /// of the http scheme (http requires a host). + /// We could provide this by allowing a pluggable 'scheme' policy-class, which would provide + /// extra capability for validating and canonicalizing a uri according to scheme, and would + /// introduce a layer of type-safety for uris of differing schemes, and thus differing semantics. + /// + /// One issue with implementing a scheme-independent uri facility is that of comparing for equality. + /// For instance, these uris are considered equal 'http://msn.com', 'http://msn.com:80'. That is -- + /// the 'default' port can be either omitted or explicit. Since we don't have a way to map a scheme + /// to it's default port, we don't have a way to know these are equal. This is just one of a class of + /// issues with regard to scheme-specific behavior. + /// + class uri + { + public: + + /// + /// The various components of a URI. This enum is used to indicate which + /// URI component is being encoded to the encode_uri_component. This allows + /// specific encoding to be performed. + /// + /// Scheme and port don't allow '%' so they don't need to be encoded. + /// + class components + { + public: + enum component + { + user_info, + host, + path, + query, + fragment, + full_uri + }; + }; + + /// + /// Encodes a URI component according to RFC 3986. + /// Note if a full URI is specified instead of an individual URI component all + /// characters not in the unreserved set are escaped. + /// + /// The URI as a string. + /// The encoded string. + ODATACPP_API static utility::string_t __cdecl encode_uri(const utility::string_t &raw, uri::components::component = components::full_uri); + + /// + /// Encodes a string by converting all characters except for RFC 3986 unreserved characters to their + /// hexadecimal representation. + /// + /// The UTF-8 string data. + /// The encoded string. + ODATACPP_API static utility::string_t __cdecl encode_data_string(const utility::string_t &utf8data); + + /// + /// Decodes an encoded string. + /// + /// The URI as a string. + /// The decoded string. + ODATACPP_API static utility::string_t __cdecl decode(const utility::string_t &encoded); + +#pragma endregion + +#pragma region splitting + + /// + /// Splits a path into its hierarchical components. + /// + /// The path as a string + /// A std::vector<utility::string_t> containing the segments in the path. + ODATACPP_API static std::vector __cdecl split_path(const utility::string_t &path); + + /// + /// Splits a query into its key-value components. + /// + /// The query string + /// A std::map<utility::string_t, utility::string_t> containing the key-value components of the query. + ODATACPP_API static std::map __cdecl split_query(const utility::string_t &query); + +#pragma endregion + +#pragma region validation + + /// + /// Validates a string as a uri. + /// + /// The uri string to be validated. + /// true if the given string represents a valid URI, false otherwise. + ODATACPP_API static bool __cdecl validate(const utility::string_t &uri_string); + +#pragma endregion + +#pragma region constructors + + /// + /// Creates an empty uri + /// + uri() { m_uri = _XPLATSTR("/");}; + + /// + /// Creates a uri from the given encoded string. This will throw an exception if the string + /// does not contain a valid uri. Use uri::validate if processing user-input. + /// + /// A pointer to an encoded string to create the URI instance. + ODATACPP_API uri(const utility::char_t *uri_string); + + /// + /// Creates a uri from the given encoded string. This will throw an exception if the string + /// does not contain a valid uri. Use uri::validate if processing user-input. + /// + /// An encoded uri string to create the URI instance. + ODATACPP_API uri(const utility::string_t &uri_string); + +#pragma endregion + +#pragma region accessors + + /// + /// Get the scheme component of the URI as an encoded string. + /// + /// The URI scheme as a string. + const utility::string_t &scheme() const { return m_components.m_scheme; } + + /// + /// Get the user information component of the URI as an encoded string. + /// + /// The URI user information as a string. + const utility::string_t &user_info() const { return m_components.m_user_info; } + + /// + /// Get the host component of the URI as an encoded string. + /// + /// The URI host as a string. + const utility::string_t &host() const { return m_components.m_host; } + + /// + /// Get the port component of the URI. Returns -1 if no port is specified. + /// + /// The URI port as an integer. + int port() const { return m_components.m_port; } + + /// + /// Get the path component of the URI as an encoded string. + /// + /// The URI path as a string. + const utility::string_t &path() const { return m_components.m_path; } + + /// + /// Get the query component of the URI as an encoded string. + /// + /// The URI query as a string. + const utility::string_t &query() const { return m_components.m_query; } + + /// + /// Get the fragment component of the URI as an encoded string. + /// + /// The URI fragment as a string. + const utility::string_t &fragment() const { return m_components.m_fragment; } + + /// + /// Creates a new uri object with the same authority portion as this one, omitting the resource and query portions. + /// + /// The new uri object with the same authority. + ODATACPP_API uri authority() const; + + /// + /// Gets the path, query, and fragment portion of this uri, which may be empty. + /// + /// The new uri object with the path, query and fragment portion of this uri. + ODATACPP_API uri resource() const; + +#pragma endregion + +#pragma region diagnostics + + /// + /// An empty uri specifies no components, and serves as a default value + /// + bool is_empty() const + { + return this->m_uri.empty() || this->m_uri == _XPLATSTR("/"); + } + + /// + /// A loopback URI is one which refers to a hostname or ip address with meaning only on the local machine. + /// + /// + /// Examples include "locahost", or ip addresses in the loopback range (127.0.0.0/24). + /// + /// true if this URI references the local host, false otherwise. + bool is_host_loopback() const + { + return !is_empty() && ((host() == _XPLATSTR("localhost")) || (host().size() > 4 && host().substr(0,4) == _XPLATSTR("127."))); + } + + /// + /// A wildcard URI is one which refers to all hostnames that resolve to the local machine (using the * or +) + /// + /// + /// http://*:80 + /// + bool is_host_wildcard() const + { + return !is_empty() && (this->host() == _XPLATSTR("*") || this->host() == _XPLATSTR("+")); + } + + /// + /// A portable URI is one with a hostname that can be resolved globally (used from another machine). + /// + /// true if this URI can be resolved globally (used from another machine), false otherwise. + /// + /// The hostname "localhost" is a reserved name that is guaranteed to resolve to the local machine, + /// and cannot be used for inter-machine communication. Likewise the hostnames "*" and "+" on Windows + /// represent wildcards, and do not map to a resolvable address. + /// + bool is_host_portable() const + { + return !(is_empty() || is_host_loopback() || is_host_wildcard()); + } + + // + /// A default port is one where the port is unspecified, and will be determined by the operating system. + /// The choice of default port may be dictated by the scheme (http -> 80) or not. + /// + bool is_port_default() const + { + return !is_empty() && this->port() == 0; + } + + /// + /// An "authority" uri is one with only a scheme, optional userinfo, hostname, and (optional) port. + /// + /// true if this is an "authority" uri, false otherwise. + bool is_authority() const + { + return !is_empty() && is_path_empty() && query().empty() && fragment().empty(); + } + + /// + /// Returns whether the other uri has the same authority as this one + /// + /// The uri to compare the authority with. + /// true if both the uri's have the same authority, false otherwise. + bool has_same_authority(const uri &other) const + { + return !is_empty() && this->authority() == other.authority(); + } + + /// + /// Returns whether the path portion of this uri is empty + /// + /// true if the path portion of this uri is empty, false otherwise. + bool is_path_empty() const + { + return path().empty() || path() == _XPLATSTR("/"); + } + +#pragma endregion + +#pragma region conversion + + /// + /// Returns the full (encoded) uri as a string. + /// + /// The full encoded uri string. + utility::string_t to_string() const + { + return m_uri; + } + +#pragma endregion + +#pragma region operators + + ODATACPP_API bool operator == (const uri &other) const; + + bool operator < (const uri &other) const + { + return m_uri < other.m_uri; + } + + bool operator != (const uri &other) const + { + return !(this->operator == (other)); + } + +#pragma endregion + + private: + friend class uri_builder; + + // Encodes all characters not in given set determined by given function. + static utility::string_t encode_impl(const utility::string_t &raw, const std::function& should_encode); + + utility::string_t m_uri; + details::_uri_components m_components; + }; + +} // namespace web +} diff --git a/include/odata/common/basic_types.h b/include/odata/common/basic_types.h new file mode 100644 index 0000000..845a937 --- /dev/null +++ b/include/odata/common/basic_types.h @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include +#include "odata/common/platform.h" + +namespace odata { namespace utility +{ + +#ifdef _MS_WINDOWS +#define _UTF16_STRINGS +#endif + +#ifdef _UTF16_STRINGS +// +// On Windows, all strings are wide +// +typedef wchar_t char_t ; +typedef std::wstring string_t; +#define _XPLATSTR(x) L ## x +typedef std::wostringstream ostringstream_t; +typedef std::wofstream ofstream_t; +typedef std::wostream ostream_t; +typedef std::wistream istream_t; +typedef std::wifstream ifstream_t; +typedef std::wistringstream istringstream_t; +typedef std::wstringstream stringstream_t; +#define ucout std::wcout +#define ucin std::wcin +#define ucerr std::wcerr +#else +// +// On POSIX platforms, all strings are narrow +// +typedef char char_t; +typedef std::string string_t; +#define _XPLATSTR(x) x +typedef std::ostringstream ostringstream_t; +typedef std::ofstream ofstream_t; +typedef std::ostream ostream_t; +typedef std::istream istream_t; +typedef std::ifstream ifstream_t; +typedef std::istringstream istringstream_t; +typedef std::stringstream stringstream_t; +#define ucout std::cout +#define ucin std::cin +#define ucerr std::cerr +#endif // endif _UTF16_STRINGS + +#ifndef _TURN_OFF_PLATFORM_STRING +#define U(x) _XPLATSTR(x) +#endif // !_TURN_OFF_PLATFORM_STRING + +}} \ No newline at end of file diff --git a/include/odata/common/compat/SafeInt3.hpp b/include/odata/common/compat/SafeInt3.hpp new file mode 100644 index 0000000..3884c10 --- /dev/null +++ b/include/odata/common/compat/SafeInt3.hpp @@ -0,0 +1,6891 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#ifndef SAFEINT_HPP +#define SAFEINT_HPP + +// Enable compiling with /Wall under VC +#if !defined __GNUC__ +#pragma warning( push ) +// Disable warnings coming from headers +#pragma warning( disable:4987 4820 4987 4820 ) +#endif + +#include +// Need this for ptrdiff_t on some compilers +#include + +#if !defined __GNUC__ && defined _M_AMD64 +#include +#define SAFEINT_USE_INTRINSICS 1 +#else +#define SAFEINT_USE_INTRINSICS 0 +#endif + +#if !defined __GNUC__ +#pragma warning( pop ) +#endif + +// Various things needed for GCC & clang +// Note: Clang is clever and claims to be GCC by defining __GNUC__ +#if defined __GNUC__ + +#define NEEDS_INT_DEFINED + +#if !defined NULL +#define NULL 0 +#endif + +#include + +#endif + +// This is only for GCC, not Clang. +#if defined __GNUC__ && !defined __clang__ +#pragma GCC diagnostic ignored "-Wunused-local-typedefs" +#endif + + +// If the user made a choice, respect it #if !defined +#if !defined NEEDS_NULLPTR_DEFINED +// Visual Studio 2010 and higher support this +#if defined(_MSC_VER) +#if (_MSC_VER < 1600) +#define NEEDS_NULLPTR_DEFINED 1 +#else +#define NEEDS_NULLPTR_DEFINED 0 +#endif +#else +// Let everything else trigger based on whether we have nullptr_t +#if defined nullptr_t +#define NEEDS_NULLPTR_DEFINED 0 +#else +#define NEEDS_NULLPTR_DEFINED 1 +#endif +#endif +#endif + +#if NEEDS_NULLPTR_DEFINED +#define nullptr NULL +#endif + + +#ifndef C_ASSERT +#define C_ASSERT(e) typedef char __C_ASSERT__[(e)?1:-1] +#endif + +// Let's test some assumptions +// We're assuming two's complement negative numbers +C_ASSERT( -1 == (int)0xffffffff ); + +/************* Compiler Options ***************************************************************************************************** + +SafeInt supports several compile-time options that can change the behavior of the class. + +Compiler options: +SAFEINT_WARN_64BIT_PORTABILITY - this re-enables various warnings that happen when /Wp64 is used. Enabling this option is not +recommended. +NEEDS_INT_DEFINED - if your compiler does not support __int8, __int16, __int32 and __int64, you can enable this. +SAFEINT_ASSERT_ON_EXCEPTION - it is often easier to stop on an assert and figure out a problem than to try and figure out +how you landed in the catch block. +SafeIntDefaultExceptionHandler - if you'd like to replace the exception handlers SafeInt provides, define your replacement and +define this. +SAFEINT_DISALLOW_UNSIGNED_NEGATION - Invoking the unary negation operator creates warnings, but if you'd like it to completely fail +to compile, define this. +ANSI_CONVERSIONS - This changes the class to use default comparison behavior, which may be unsafe. Enabling this +option is not recommended. +SAFEINT_DISABLE_BINARY_ASSERT - binary AND, OR or XOR operations on mixed size types can produce unexpected results. If you do +this, the default is to assert. Set this if you prefer not to assert under these conditions. +SIZE_T_CAST_NEEDED - some compilers complain if there is not a cast to size_t, others complain if there is one. +This lets you not have your compiler complain. +SAFEINT_DISABLE_SHIFT_ASSERT - Set this option if you don't want to assert when shifting more bits than the type has. Enabling +this option is not recommended. + +************************************************************************************************************************************/ + +/* +* The SafeInt class is designed to have as low an overhead as possible +* while still ensuring that all integer operations are conducted safely. +* Nearly every operator has been overloaded, with a very few exceptions. +* +* A usability-safety trade-off has been made to help ensure safety. This +* requires that every operation return either a SafeInt or a bool. If we +* allowed an operator to return a base integer type T, then the following +* can happen: +* +* char i = SafeInt(32) * 2 + SafeInt(16) * 4; +* +* The * operators take precedence, get overloaded, return a char, and then +* you have: +* +* char i = (char)64 + (char)64; //overflow! +* +* This situation would mean that safety would depend on usage, which isn't +* acceptable. +* +* One key operator that is missing is an implicit cast to type T. The reason for +* this is that if there is an implicit cast operator, then we end up with +* an ambiguous compile-time precedence. Because of this amiguity, there +* are two methods that are provided: +* +* Casting operators for every native integer type +* Version 3 note - it now compiles correctly for size_t without warnings +* +* SafeInt::Ptr() - returns the address of the internal integer +* Note - the '&' (address of) operator has been overloaded and returns +* the address of the internal integer. +* +* The SafeInt class should be used in any circumstances where ensuring +* integrity of the calculations is more important than performance. See Performance +* Notes below for additional information. +* +* Many of the conditionals will optimize out or be inlined for a release +* build (especially with /Ox), but it does have significantly more overhead, +* especially for signed numbers. If you do not _require_ negative numbers, use +* unsigned integer types - certain types of problems cannot occur, and this class +* performs most efficiently. +* +* Here's an example of when the class should ideally be used - +* +* void* AllocateMemForStructs(int StructSize, int HowMany) +* { +* SafeInt s(StructSize); +* +* s *= HowMany; +* +* return malloc(s); +* +* } +* +* Here's when it should NOT be used: +* +* void foo() +* { +* int i; +* +* for(i = 0; i < 0xffff; i++) +* .... +* } +* +* Error handling - a SafeInt class will throw exceptions if something +* objectionable happens. The exceptions are SafeIntException classes, +* which contain an enum as a code. +* +* Typical usage might be: +* +* bool foo() +* { +* SafeInt s; //note that s == 0 unless set +* +* try{ +* s *= 23; +* .... +* } +* catch(SafeIntException err) +* { +* //handle errors here +* } +* } +* +* Update for 3.0 - the exception class is now a template parameter. +* You can replace the exception class with any exception class you like. This is accomplished by: +* 1) Create a class that has the following interface: +* +template <> class YourSafeIntExceptionHandler < YourException > +{ +public: +static __declspec(noreturn) void __stdcall SafeIntOnOverflow() +{ +throw YourException( YourSafeIntArithmeticOverflowError ); +} + +static __declspec(noreturn) void __stdcall SafeIntOnDivZero() +{ +throw YourException( YourSafeIntDivideByZeroError ); +} +}; +* +* Note that you don't have to throw C++ exceptions, you can throw Win32 exceptions, or do +* anything you like, just don't return from the call back into the code. +* +* 2) Either explicitly declare SafeInts like so: +* SafeInt< int, YourSafeIntExceptionHandler > si; +* or +* #define SafeIntDefaultExceptionHandler YourSafeIntExceptionHandler +* +* Performance: +* +* Due to the highly nested nature of this class, you can expect relatively poor +* performance in unoptimized code. In tests of optimized code vs. correct inline checks +* in native code, this class has been found to take approximately 8% more CPU time (this varies), +* most of which is due to exception handling. Solutions: +* +* 1) Compile optimized code - /Ox is best, /O2 also performs well. Interestingly, /O1 +* (optimize for size) does not work as well. +* 2) If that 8% hit is really a serious problem, walk through the code and inline the +* exact same checks as the class uses. +* 3) Some operations are more difficult than others - avoid using signed integers, and if +* possible keep them all the same size. 64-bit integers are also expensive. Mixing +* different integer sizes and types may prove expensive. Be aware that literals are +* actually ints. For best performance, cast literals to the type desired. +* +* +* Performance update +* The current version of SafeInt uses template specialization to force the compiler to invoke only the +* operator implementation needed for any given pair of types. This will dramatically improve the perf +* of debug builds. +* +* 3.0 update - not only have we maintained the specialization, there were some cases that were overly complex, +* and using some additional cases (e.g. signed __int64 and unsigned __int64) resulted in some simplification. +* Additionally, there was a lot of work done to better optimize the 64-bit multiplication. +* +* Binary Operators +* +* All of the binary operators have certain assumptions built into the class design. +* This is to ensure correctness. Notes on each class of operator follow: +* +* Arithmetic Operators (*,/,+,-,%) +* There are three possible variants: +* SafeInt< T, E > op SafeInt< T, E > +* SafeInt< T, E > op U +* U op SafeInt< T, E > +* +* The SafeInt< T, E > op SafeInt< U, E > variant is explicitly not supported, and if you try to do +* this the compiler with throw the following error: +* +* error C2593: 'operator *' is ambiguous +* +* This is because the arithmetic operators are required to return a SafeInt of some type. +* The compiler cannot know whether you'd prefer to get a type T or a type U returned. If +* you need to do this, you need to extract the value contained within one of the two using +* the casting operator. For example: +* +* SafeInt< T, E > t, result; +* SafeInt< U, E > u; +* +* result = t * (U)u; +* +* Comparison Operators +* Because each of these operators return type bool, mixing SafeInts of differing types is +* allowed. +* +* Shift Operators +* Shift operators always return the type on the left hand side of the operator. Mixed type +* operations are allowed because the return type is always known. +* +* Boolean Operators +* Like comparison operators, these overloads always return type bool, and mixed-type SafeInts +* are allowed. Additionally, specific overloads exist for type bool on both sides of the +* operator. +* +* Binary Operators +* Mixed-type operations are discouraged, however some provision has been made in order to +* enable things like: +* +* SafeInt c = 2; +* +* if(c & 0x02) +* ... +* +* The "0x02" is actually an int, and it needs to work. +* In the case of binary operations on integers smaller than 32-bit, or of mixed type, corner +* cases do exist where you could get unexpected results. In any case where SafeInt returns a different +* result than the underlying operator, it will call assert(). You should examine your code and cast things +* properly so that you are not programming with side effects. +* +* Documented issues: +* +* This header compiles correctly at /W4 using VC++ 8 (Version 14.00.50727.42) and later. +* As of this writing, I believe it will also work for VC 7.1, but not for VC 7.0 or below. +* If you need a version that will work with lower level compilers, try version 1.0.7. None +* of them work with Visual C++ 6, and gcc didn't work very well, either, though this hasn't +* been tried recently. +* +* It is strongly recommended that any code doing integer manipulation be compiled at /W4 +* - there are a number of warnings which pertain to integer manipulation enabled that are +* not enabled at /W3 (default for VC++) +* +* Perf note - postfix operators are slightly more costly than prefix operators. +* Unless you're actually assigning it to something, ++SafeInt is less expensive than SafeInt++ +* +* The comparison operator behavior in this class varies from the ANSI definition, which is +* arguably broken. As an example, consider the following: +* +* unsigned int l = 0xffffffff; +* char c = -1; +* +* if(c == l) +* printf("Why is -1 equal to 4 billion???\n"); +* +* The problem here is that c gets cast to an int, now has a value of 0xffffffff, and then gets +* cast again to an unsigned int, losing the true value. This behavior is despite the fact that +* an __int64 exists, and the following code will yield a different (and intuitively correct) +* answer: +* +* if((__int64)c == (__int64)l)) +* printf("Why is -1 equal to 4 billion???\n"); +* else +* printf("Why doesn't the compiler upcast to 64-bits when needed?\n"); +* +* Note that combinations with smaller integers won't display the problem - if you +* changed "unsigned int" above to "unsigned short", you'd get the right answer. +* +* If you prefer to retain the ANSI standard behavior insert +* #define ANSI_CONVERSIONS +* into your source. Behavior differences occur in the following cases: +* 8, 16, and 32-bit signed int, unsigned 32-bit int +* any signed int, unsigned 64-bit int +* Note - the signed int must be negative to show the problem +* +* +* Revision history: +* +* Oct 12, 2003 - Created +* Author - David LeBlanc - dleblanc@microsoft.com +* +* Oct 27, 2003 - fixed numerous items pointed out by michmarc and bdawson +* Dec 28, 2003 - 1.0 +* added support for mixed-type operations +* thanks to vikramh +* also fixed broken __int64 multiplication section +* added extended support for mixed-type operations where possible +* Jan 28, 2004 - 1.0.1 +* changed WCHAR to wchar_t +* fixed a construct in two mixed-type assignment overloads that was +* not compiling on some compilers +* Also changed name of private method to comply with standards on +* reserved names +* Thanks to Niels Dekker for the input +* Feb 12, 2004 - 1.0.2 +* Minor changes to remove dependency on Windows headers +* Consistently used __int16, __int32 and __int64 to ensure +* portability +* May 10, 2004 - 1.0.3 +* Corrected bug in one case of GreaterThan +* July 22, 2004 - 1.0.4 +* Tightened logic in addition check (saving 2 instructions) +* Pulled error handler out into function to enable user-defined replacement +* Made internal type of SafeIntException an enum (as per Niels' suggestion) +* Added casts for base integer types (as per Scott Meyers' suggestion) +* Updated usage information - see important new perf notes. +* Cleaned up several const issues (more thanks to Niels) +* +* Oct 1, 2004 - 1.0.5 +* Added support for SEH exceptions instead of C++ exceptions - Win32 only +* Made handlers for DIV0 and overflows individually overridable +* Commented out the destructor - major perf gains here +* Added cast operator for type long, since long != __int32 +* Corrected a couple of missing const modifiers +* Fixed broken >= and <= operators for type U op SafeInt< T, E > +* Nov 5, 2004 - 1.0.6 +* Implemented new logic in binary operators to resolve issues with +* implicit casts +* Fixed casting operator because char != signed char +* Defined __int32 as int instead of long +* Removed unsafe SafeInt::Value method +* Re-implemented casting operator as a result of removing Value method +* Dec 1, 2004 - 1.0.7 +* Implemented specialized operators for pointer arithmetic +* Created overloads for cases of U op= SafeInt. What you do with U +* after that may be dangerous. +* Fixed bug in corner case of MixedSizeModulus +* Fixed bug in MixedSizeMultiply and MixedSizeDivision with input of 0 +* Added throw() decorations +* +* Apr 12, 2005 - 2.0 +* Extensive revisions to leverage template specialization. +* April, 2007 Extensive revisions for version 3.0 +* Nov 22, 2009 Forked from MS internal code +* Changes needed to support gcc compiler - many thanks to Niels Dekker +* for determining not just the issues, but also suggesting fixes. +* Also updating some of the header internals to be the same as the upcoming Visual Studio version. +* +* Jan 16, 2010 64-bit gcc has long == __int64, which means that many of the existing 64-bit +* templates are over-specialized. This forces a redefinition of all the 64-bit +* multiplication routines to use pointers instead of references for return +* values. Also, let's use some intrinsics for x64 Microsoft compiler to +* reduce code size, and hopefully improve efficiency. +* +* Note about code style - throughout this class, casts will be written using C-style (T), +* not C++ style static_cast< T >. This is because the class is nearly always dealing with integer +* types, and in this case static_cast and a C cast are equivalent. Given the large number of casts, +* the code is a little more readable this way. In the event a cast is needed where static_cast couldn't +* be substituted, we'll use the new templatized cast to make it explicit what the operation is doing. +* +************************************************************************************************************ +* Version 3.0 changes: +* +* 1) The exception type thrown is now replacable, and you can throw your own exception types. This should help +* those using well-developed exception classes. +* 2) The 64-bit multiplication code has had a lot of perf work done, and should be faster than 2.0. +* 3) There is now limited floating point support. You can initialize a SafeInt with a floating point type, +* and you can cast it out (or assign) to a float as well. +* 4) There is now an Align method. I noticed people use this a lot, and rarely check errors, so now you have one. +* +* Another major improvement is the addition of external functions - if you just want to check an operation, this can now happen: +* All of the following can be invoked without dealing with creating a class, or managing exceptions. This is especially handy +* for 64-bit porting, since SafeCast compiles away for a 32-bit cast from size_t to unsigned long, but checks it for 64-bit. +* +* inline bool SafeCast( const T From, U& To ) throw() +* inline bool SafeEquals( const T t, const U u ) throw() +* inline bool SafeNotEquals( const T t, const U u ) throw() +* inline bool SafeGreaterThan( const T t, const U u ) throw() +* inline bool SafeGreaterThanEquals( const T t, const U u ) throw() +* inline bool SafeLessThan( const T t, const U u ) throw() +* inline bool SafeLessThanEquals( const T t, const U u ) throw() +* inline bool SafeModulus( const T& t, const U& u, T& result ) throw() +* inline bool SafeMultiply( T t, U u, T& result ) throw() +* inline bool SafeDivide( T t, U u, T& result ) throw() +* inline bool SafeAdd( T t, U u, T& result ) throw() +* inline bool SafeSubtract( T t, U u, T& result ) throw() +* +*/ + +//use these if the compiler does not support _intXX +#ifdef NEEDS_INT_DEFINED +#define __int8 char +#define __int16 short +#define __int32 int +#define __int64 long long +#endif + +// GCC can't tell that the exception function won't return, +// but Visual Studio can +#if defined __GNUC__ +#define NotReachedReturn(x) return(x) +#else +#define NotReachedReturn(x) +#endif + +/* catch these to handle errors +** Currently implemented code values: +** ERROR_ARITHMETIC_OVERFLOW +** EXCEPTION_INT_DIVIDE_BY_ZERO +*/ + +enum SafeIntError +{ + SafeIntNoError = 0, + SafeIntArithmeticOverflow, + SafeIntDivideByZero +}; + +/* +* Error handler classes +* Using classes to deal with exceptions is going to allow the most +* flexibility, and we can mix different error handlers in the same project +* or even the same file. It isn't advisable to do this in the same function +* because a SafeInt< int, MyExceptionHandler > isn't the same thing as +* SafeInt< int, YourExceptionHander >. +* If for some reason you have to translate between the two, cast one of them back to its +* native type. +* +* To use your own exception class with SafeInt, first create your exception class, +* which may look something like the SafeIntException class below. The second step is to +* create a template specialization that implements SafeIntOnOverflow and SafeIntOnDivZero. +* For example: +* +* template <> class SafeIntExceptionHandler < YourExceptionClass > +* { +* static __declspec(noreturn) void __stdcall SafeIntOnOverflow() +* { +* throw YourExceptionClass( EXCEPTION_INT_OVERFLOW ); +* } +* +* static __declspec(noreturn) void __stdcall SafeIntOnDivZero() +* { +* throw YourExceptionClass( EXCEPTION_INT_DIVIDE_BY_ZERO ); +* } +* }; +* +* typedef SafeIntExceptionHandler < YourExceptionClass > YourSafeIntExceptionHandler +* You'd then declare your SafeInt objects like this: +* SafeInt< int, YourSafeIntExceptionHandler > +* +* Unfortunately, there is no such thing as partial template specialization in typedef +* statements, so you have three options if you find this cumbersome: +* +* 1) Create a holder class: +* +* template < typename T > +* class MySafeInt +* { +* public: +* SafeInt< T, MyExceptionClass> si; +* }; +* +* You'd then declare an instance like so: +* MySafeInt< int > i; +* +* You'd lose handy things like initialization - it would have to be initialized as: +* +* i.si = 0; +* +* 2) You could create a typedef for every int type you deal with: +* +* typedef SafeInt< int, MyExceptionClass > MySafeInt; +* typedef SafeInt< char, MyExceptionClass > MySafeChar; +* +* and so on. The second approach is probably more usable, and will just drop into code +* better, which is the original intent of the SafeInt class. +* +* 3) If you're going to consistently use a different class to handle your exceptions, +* you can override the default typedef like so: +* +* #define SafeIntDefaultExceptionHandler YourSafeIntExceptionHandler +* +* Overall, this is probably the best approach. +* */ + +class SafeIntException +{ +public: + SafeIntException() { m_code = SafeIntNoError; } + SafeIntException( SafeIntError code ) + { + m_code = code; + } + SafeIntError m_code; +}; + +#if defined SAFEINT_ASSERT_ON_EXCEPTION +inline void SafeIntExceptionAssert(){ assert(false); } +#else +inline void SafeIntExceptionAssert(){} +#endif + +namespace SafeIntInternal +{ + template < typename E > class SafeIntExceptionHandler; + + template <> class SafeIntExceptionHandler < SafeIntException > + { + public: +#if defined __GNUC__ + static __attribute__((noreturn)) void SafeIntOnOverflow() +#else + static __declspec(noreturn) void __stdcall SafeIntOnOverflow() +#endif + { + SafeIntExceptionAssert(); + throw SafeIntException( SafeIntArithmeticOverflow ); + } + +#if defined __GNUC__ + static __attribute__((noreturn)) void SafeIntOnDivZero() +#else + static __declspec(noreturn) void __stdcall SafeIntOnDivZero() +#endif + { + SafeIntExceptionAssert(); + throw SafeIntException( SafeIntDivideByZero ); + } + }; + +#if defined _WINDOWS_ + class SafeIntWin32Exception + { + public: + SafeIntWin32Exception( DWORD dwExceptionCode, DWORD dwExceptionFlags = EXCEPTION_NONCONTINUABLE ) + { + SafeIntExceptionAssert(); + RaiseException( dwExceptionCode, dwExceptionFlags, 0, 0 ); + } + }; + + template <> class SafeIntExceptionHandler < SafeIntWin32Exception > + { + public: + static __declspec(noreturn) void __stdcall SafeIntOnOverflow() + { + SafeIntExceptionAssert(); + SafeIntWin32Exception( (DWORD)EXCEPTION_INT_OVERFLOW ); + } + + static __declspec(noreturn) void __stdcall SafeIntOnDivZero() + { + SafeIntExceptionAssert(); + SafeIntWin32Exception( (DWORD)EXCEPTION_INT_DIVIDE_BY_ZERO ); + } + }; +#endif +} // namespace SafeIntInternal + +typedef SafeIntInternal::SafeIntExceptionHandler < SafeIntException > CPlusPlusExceptionHandler; +#if defined _WINDOWS_ +typedef SafeIntInternal::SafeIntExceptionHandler < SafeIntInternal::SafeIntWin32Exception > Win32ExceptionHandler; +#endif + +// If the user hasn't defined a default exception handler, +// define one now, depending on whether they would like Win32 or C++ exceptions +#if !defined SafeIntDefaultExceptionHandler +#if defined SAFEINT_RAISE_EXCEPTION +#if !defined _WINDOWS_ +#error Include windows.h in order to use Win32 exceptions +#endif + +#define SafeIntDefaultExceptionHandler Win32ExceptionHandler +#else +#define SafeIntDefaultExceptionHandler CPlusPlusExceptionHandler +#endif +#endif + +/* +* Turns out we can fool the compiler into not seeing compile-time constants with +* a simple template specialization +*/ +template < int method > class CompileConst; +template <> class CompileConst { public: static bool Value(){ return true; } }; +template <> class CompileConst { public: static bool Value(){ return false; } }; + +/* +* The following template magic is because we're now not allowed +* to cast a float to an enum. This means that if we happen to assign +* an enum to a SafeInt of some type, it won't compile, unless we prevent +* isFloat = ( (T)( (float)1.1 ) > (T)1 ) +* from compiling in the case of an enum, which is the point of the specialization +* that follows. +*/ + +template < typename T > class NumericType; + +template <> class NumericType { public: enum{ isBool = true, isFloat = false, isInt = false }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +#if defined SAFEINT_USE_WCHAR_T || _NATIVE_WCHAR_T_DEFINED +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +#endif +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType<__int64> { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = false, isInt = true }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = true, isInt = false }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = true, isInt = false }; }; +template <> class NumericType { public: enum{ isBool = false, isFloat = true, isInt = false }; }; +// Catch-all for anything not supported +template < typename T > class NumericType { public: enum{ isBool = false, isFloat = false, isInt = false }; }; + +// Use this to avoid compile-time const truncation warnings +template < int fSigned, int bits > class MinMax; + +template <> class MinMax< true, 8 > { public: const static __int8 min = (-0x7f - 1); +const static __int8 max = 0x7f; }; +template <> class MinMax< true, 16 > { public: const static __int16 min = ( -0x7fff - 1 ); +const static __int16 max = 0x7fff; }; +template <> class MinMax< true, 32 > { public: const static __int32 min = ( -0x7fffffff -1 ); +const static __int32 max = 0x7fffffff; }; +template <> class MinMax< true, 64 > { public: const static __int64 min = static_cast<__int64>(0x8000000000000000LL); +const static __int64 max = 0x7fffffffffffffffLL; }; + +template <> class MinMax< false, 8 > { public: const static unsigned __int8 min = 0; +const static unsigned __int8 max = 0xff; }; +template <> class MinMax< false, 16 > { public: const static unsigned __int16 min = 0; +const static unsigned __int16 max = 0xffff; }; +template <> class MinMax< false, 32 > { public: const static unsigned __int32 min = 0; +const static unsigned __int32 max = 0xffffffff; }; +template <> class MinMax< false, 64 > { public: const static unsigned __int64 min = 0; +const static unsigned __int64 max = 0xffffffffffffffffULL; }; + +template < typename T > class IntTraits +{ +public: + C_ASSERT( NumericType::isInt ); + enum + { + isSigned = ( (T)(-1) < 0 ), + is64Bit = ( sizeof(T) == 8 ), + is32Bit = ( sizeof(T) == 4 ), + is16Bit = ( sizeof(T) == 2 ), + is8Bit = ( sizeof(T) == 1 ), + isLT32Bit = ( sizeof(T) < 4 ), + isLT64Bit = ( sizeof(T) < 8 ), + isInt8 = ( sizeof(T) == 1 && isSigned ), + isUint8 = ( sizeof(T) == 1 && !isSigned ), + isInt16 = ( sizeof(T) == 2 && isSigned ), + isUint16 = ( sizeof(T) == 2 && !isSigned ), + isInt32 = ( sizeof(T) == 4 && isSigned ), + isUint32 = ( sizeof(T) == 4 && !isSigned ), + isInt64 = ( sizeof(T) == 8 && isSigned ), + isUint64 = ( sizeof(T) == 8 && !isSigned ), + bitCount = ( sizeof(T)*8 ), + isBool = ( (T)2 == (T)1 ) + }; + + // On version 13.10 enums cannot define __int64 values + // so we'll use const statics instead! + const static T maxInt = MinMax< isSigned, bitCount >::max; + const static T minInt = MinMax< isSigned, bitCount >::min; +}; + +template < typename T > +const T IntTraits< T >::maxInt; +template < typename T > +const T IntTraits< T >::minInt; + +template < typename T, typename U > class SafeIntCompare +{ +public: + enum + { + isBothSigned = (IntTraits< T >::isSigned && IntTraits< U >::isSigned), + isBothUnsigned = (!IntTraits< T >::isSigned && !IntTraits< U >::isSigned), + isLikeSigned = ((bool)(IntTraits< T >::isSigned) == (bool)(IntTraits< U >::isSigned)), + isCastOK = ((isLikeSigned && sizeof(T) >= sizeof(U)) || + (IntTraits< T >::isSigned && sizeof(T) > sizeof(U))), + isBothLT32Bit = (IntTraits< T >::isLT32Bit && IntTraits< U >::isLT32Bit), + isBothLT64Bit = (IntTraits< T >::isLT64Bit && IntTraits< U >::isLT64Bit) + }; +}; + +//all of the arithmetic operators can be solved by the same code within +//each of these regions without resorting to compile-time constant conditionals +//most operators collapse the problem into less than the 22 zones, but this is used +//as the first cut +//using this also helps ensure that we handle all of the possible cases correctly + +template < typename T, typename U > class IntRegion +{ +public: + enum + { + //unsigned-unsigned zone + IntZone_UintLT32_UintLT32 = SafeIntCompare< T,U >::isBothUnsigned && SafeIntCompare< T,U >::isBothLT32Bit, + IntZone_Uint32_UintLT64 = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::is32Bit && IntTraits< U >::isLT64Bit, + IntZone_UintLT32_Uint32 = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::isLT32Bit && IntTraits< U >::is32Bit, + IntZone_Uint64_Uint = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::is64Bit, + IntZone_UintLT64_Uint64 = SafeIntCompare< T,U >::isBothUnsigned && IntTraits< T >::isLT64Bit && IntTraits< U >::is64Bit, + //unsigned-signed + IntZone_UintLT32_IntLT32 = !IntTraits< T >::isSigned && IntTraits< U >::isSigned && SafeIntCompare< T,U >::isBothLT32Bit, + IntZone_Uint32_IntLT64 = IntTraits< T >::isUint32 && IntTraits< U >::isSigned && IntTraits< U >::isLT64Bit, + IntZone_UintLT32_Int32 = !IntTraits< T >::isSigned && IntTraits< T >::isLT32Bit && IntTraits< U >::isInt32, + IntZone_Uint64_Int = IntTraits< T >::isUint64 && IntTraits< U >::isSigned && IntTraits< U >::isLT64Bit, + IntZone_UintLT64_Int64 = !IntTraits< T >::isSigned && IntTraits< T >::isLT64Bit && IntTraits< U >::isInt64, + IntZone_Uint64_Int64 = IntTraits< T >::isUint64 && IntTraits< U >::isInt64, + //signed-signed + IntZone_IntLT32_IntLT32 = SafeIntCompare< T,U >::isBothSigned && ::SafeIntCompare< T, U >::isBothLT32Bit, + IntZone_Int32_IntLT64 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::is32Bit && IntTraits< U >::isLT64Bit, + IntZone_IntLT32_Int32 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::isLT32Bit && IntTraits< U >::is32Bit, + IntZone_Int64_Int64 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::isInt64 && IntTraits< U >::isInt64, + IntZone_Int64_Int = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::is64Bit && IntTraits< U >::isLT64Bit, + IntZone_IntLT64_Int64 = SafeIntCompare< T,U >::isBothSigned && IntTraits< T >::isLT64Bit && IntTraits< U >::is64Bit, + //signed-unsigned + IntZone_IntLT32_UintLT32 = IntTraits< T >::isSigned && !IntTraits< U >::isSigned && SafeIntCompare< T,U >::isBothLT32Bit, + IntZone_Int32_UintLT32 = IntTraits< T >::isInt32 && !IntTraits< U >::isSigned && IntTraits< U >::isLT32Bit, + IntZone_IntLT64_Uint32 = IntTraits< T >::isSigned && IntTraits< T >::isLT64Bit && IntTraits< U >::isUint32, + IntZone_Int64_UintLT64 = IntTraits< T >::isInt64 && !IntTraits< U >::isSigned && IntTraits< U >::isLT64Bit, + IntZone_Int_Uint64 = IntTraits< T >::isSigned && IntTraits< U >::isUint64 && IntTraits< T >::isLT64Bit, + IntZone_Int64_Uint64 = IntTraits< T >::isInt64 && IntTraits< U >::isUint64 + }; +}; + + +// In all of the following functions, we have two versions +// One for SafeInt, which throws C++ (or possibly SEH) exceptions +// The non-throwing versions are for use by the helper functions that return success and failure. +// Some of the non-throwing functions are not used, but are maintained for completeness. + +// There's no real alternative to duplicating logic, but keeping the two versions +// immediately next to one another will help reduce problems + + +// useful function to help with getting the magnitude of a negative number +enum AbsMethod +{ + AbsMethodInt, + AbsMethodInt64, + AbsMethodNoop +}; + +template < typename T > +class GetAbsMethod +{ +public: + enum + { + method = IntTraits< T >::isLT64Bit && IntTraits< T >::isSigned ? AbsMethodInt : + IntTraits< T >::isInt64 ? AbsMethodInt64 : AbsMethodNoop + }; +}; + +// let's go ahead and hard-code a dependency on the +// representation of negative numbers to keep compilers from getting overly +// happy with optimizing away things like -MIN_INT. +template < typename T, int > class AbsValueHelper; + +template < typename T > class AbsValueHelper < T, AbsMethodInt> +{ +public: + static unsigned __int32 Abs( T t ) throw() + { + assert( t < 0 ); + return ~(unsigned __int32)t + 1; + } +}; + +template < typename T > class AbsValueHelper < T, AbsMethodInt64 > +{ +public: + static unsigned __int64 Abs( T t ) throw() + { + assert( t < 0 ); + return ~(unsigned __int64)t + 1; + } +}; + +template < typename T > class AbsValueHelper < T, AbsMethodNoop > +{ +public: + static T Abs( T t ) throw() + { + // Why are you calling Abs on an unsigned number ??? + assert( false ); + return t; + } +}; + +template < typename T, bool > class NegationHelper; + +template < typename T > class NegationHelper // Signed +{ +public: + template + static T NegativeThrow( T t ) + { + // corner case + if( t != IntTraits< T >::minInt ) + { + // cast prevents unneeded checks in the case of small ints + return -t; + } + E::SafeIntOnOverflow(); + } + + static bool Negative( T t, T& ret ) throw() + { + // corner case + if( t != IntTraits< T >::minInt ) + { + // cast prevents unneeded checks in the case of small ints + ret = -t; + return true; + } + return false; + } +}; + +// Helper classes to work keep compilers from +// optimizing away negation +template < typename T > class SignedNegation; + +template <> +class SignedNegation +{ +public: + static signed __int32 Value(unsigned __int64 in) + { + return (signed __int32)(~(unsigned __int32)in + 1); + } + + static signed __int32 Value(unsigned __int32 in) + { + return (signed __int32)(~in + 1); + } +}; + +template <> +class SignedNegation +{ +public: + static signed __int64 Value(unsigned __int64 in) + { + return (signed __int64)(~in + 1); + } +}; + +template < int method > class NegationAssertHelper; + +template<> +class NegationAssertHelper< true > +{ +public: + static void BehaviorWarning() + { + // This will normally upcast to int + // For example -(unsigned short)0xffff == (int)0xffff0001 + // This class will retain the type, and will truncate, which may not be what + // you wanted + // If you want normal operator casting behavior, do this: + // SafeInt ss = 0xffff; + // then: + // -(SafeInt(ss)) + // will then emit a signed int with the correct value and bitfield + assert( false ); + } +}; + +template<> +class NegationAssertHelper< false > +{ +public: + static void BehaviorWarning(){} +}; + +template < typename T > class NegationHelper // unsigned +{ +public: + template + static T NegativeThrow( T t ) throw() + { + NegationAssertHelper< IntTraits::isLT32Bit >::BehaviorWarning(); + +#if defined SAFEINT_DISALLOW_UNSIGNED_NEGATION + C_ASSERT( sizeof(T) == 0 ); +#endif + +#if !defined __GNUC__ +#pragma warning(push) + //this avoids warnings from the unary '-' operator being applied to unsigned numbers +#pragma warning(disable:4146) +#endif + // Note - this could be quenched on gcc + // by doing something like: + // return (T)-((__int64)t); + // but it seems like you would want a warning when doing this. + return (T)-t; + +#if !defined __GNUC__ +#pragma warning(pop) +#endif + } + + static bool Negative( T t, T& ret ) + { + if( IntTraits::isLT32Bit ) + { + // See above + assert( false ); + } +#if defined SAFEINT_DISALLOW_UNSIGNED_NEGATION + C_ASSERT( sizeof(T) == 0 ); +#endif + // Do it this way to avoid warning + ret = -t; + return true; + } +}; + +//core logic to determine casting behavior +enum CastMethod +{ + CastOK = 0, + CastCheckLTZero, + CastCheckGTMax, + CastCheckMinMaxUnsigned, + CastCheckMinMaxSigned, + CastToFloat, + CastFromFloat, + CastToBool, + CastFromBool +}; + + +template < typename ToType, typename FromType > +class GetCastMethod +{ +public: + enum + { + method = ( IntTraits< FromType >::isBool && + !IntTraits< ToType >::isBool ) ? CastFromBool : + + ( !IntTraits< FromType >::isBool && + IntTraits< ToType >::isBool ) ? CastToBool : + + ( SafeIntCompare< ToType, FromType >::isCastOK ) ? CastOK : + + ( ( IntTraits< ToType >::isSigned && + !IntTraits< FromType >::isSigned && + sizeof( FromType ) >= sizeof( ToType ) ) || + ( SafeIntCompare< ToType, FromType >::isBothUnsigned && + sizeof( FromType ) > sizeof( ToType ) ) ) ? CastCheckGTMax : + + ( !IntTraits< ToType >::isSigned && + IntTraits< FromType >::isSigned && + sizeof( ToType ) >= sizeof( FromType ) ) ? CastCheckLTZero : + + ( !IntTraits< ToType >::isSigned ) ? CastCheckMinMaxUnsigned + : CastCheckMinMaxSigned + }; +}; + +template < typename FromType > class GetCastMethod < float, FromType > +{ +public: + enum{ method = CastOK }; +}; + +template < typename FromType > class GetCastMethod < double, FromType > +{ +public: + enum{ method = CastOK }; +}; + +template < typename FromType > class GetCastMethod < long double, FromType > +{ +public: + enum{ method = CastOK }; +}; + +template < typename ToType > class GetCastMethod < ToType, float > +{ +public: + enum{ method = CastFromFloat }; +}; + +template < typename ToType > class GetCastMethod < ToType, double > +{ +public: + enum{ method = CastFromFloat }; +}; + +template < typename ToType > class GetCastMethod < ToType, long double > +{ +public: + enum{ method = CastFromFloat }; +}; + +template < typename T, typename U, int > class SafeCastHelper; + +template < typename T, typename U > class SafeCastHelper < T, U, CastOK > +{ +public: + static bool Cast( U u, T& t ) throw() + { + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) + { + t = (T)u; + } +}; + +// special case floats and doubles +// tolerate loss of precision +template < typename T, typename U > class SafeCastHelper < T, U, CastFromFloat > +{ +public: + static bool Cast( U u, T& t ) throw() + { + if( u <= (U)IntTraits< T >::maxInt && + u >= (U)IntTraits< T >::minInt ) + { + t = (T)u; + return true; + } + return false; + } + + template < typename E > + static void CastThrow( U u, T& t ) + { + if( u <= (U)IntTraits< T >::maxInt && + u >= (U)IntTraits< T >::minInt ) + { + t = (T)u; + return; + } + E::SafeIntOnOverflow(); + } +}; + +// Match on any method where a bool is cast to type T +template < typename T > class SafeCastHelper < T, bool, CastFromBool > +{ +public: + static bool Cast( bool b, T& t ) throw() + { + t = (T)( b ? 1 : 0 ); + return true; + } + + template < typename E > + static void CastThrow( bool b, T& t ) + { + t = (T)( b ? 1 : 0 ); + } +}; + +template < typename T > class SafeCastHelper < bool, T, CastToBool > +{ +public: + static bool Cast( T t, bool& b ) throw() + { + b = !!t; + return true; + } + + template < typename E > + static void CastThrow( bool b, T& t ) + { + b = !!t; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckLTZero > +{ +public: + static bool Cast( U u, T& t ) throw() + { + if( u < 0 ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) + { + if( u < 0 ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckGTMax > +{ +public: + static bool Cast( U u, T& t ) throw() + { + if( u > (U)IntTraits< T >::maxInt ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) + { + if( u > (U)IntTraits< T >::maxInt ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckMinMaxUnsigned > +{ +public: + static bool Cast( U u, T& t ) throw() + { + // U is signed - T could be either signed or unsigned + if( u > IntTraits< T >::maxInt || u < 0 ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) + { + // U is signed - T could be either signed or unsigned + if( u > IntTraits< T >::maxInt || u < 0 ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +template < typename T, typename U > class SafeCastHelper < T, U, CastCheckMinMaxSigned > +{ +public: + static bool Cast( U u, T& t ) throw() + { + // T, U are signed + if( u > IntTraits< T >::maxInt || u < IntTraits< T >::minInt ) + return false; + + t = (T)u; + return true; + } + + template < typename E > + static void CastThrow( U u, T& t ) + { + //T, U are signed + if( u > IntTraits< T >::maxInt || u < IntTraits< T >::minInt ) + E::SafeIntOnOverflow(); + + t = (T)u; + } +}; + +//core logic to determine whether a comparison is valid, or needs special treatment +enum ComparisonMethod +{ + ComparisonMethod_Ok = 0, + ComparisonMethod_CastInt, + ComparisonMethod_CastInt64, + ComparisonMethod_UnsignedT, + ComparisonMethod_UnsignedU +}; + +// Note - the standard is arguably broken in the case of some integer +// conversion operations +// For example, signed char a = -1 = 0xff +// unsigned int b = 0xffffffff +// If you then test if a < b, a value-preserving cast +// is made, and you're essentially testing +// (unsigned int)a < b == false +// +// I do not think this makes sense - if you perform +// a cast to an __int64, which can clearly preserve both value and signedness +// then you get a different and intuitively correct answer +// IMHO, -1 should be less than 4 billion +// If you prefer to retain the ANSI standard behavior +// insert #define ANSI_CONVERSIONS into your source +// Behavior differences occur in the following cases: +// 8, 16, and 32-bit signed int, unsigned 32-bit int +// any signed int, unsigned 64-bit int +// Note - the signed int must be negative to show the problem + +template < typename T, typename U > +class ValidComparison +{ +public: + enum + { +#ifdef ANSI_CONVERSIONS + method = ComparisonMethod_Ok +#else + method = ( ( SafeIntCompare< T, U >::isLikeSigned ) ? ComparisonMethod_Ok : + ( ( IntTraits< T >::isSigned && sizeof(T) < 8 && sizeof(U) < 4 ) || + ( IntTraits< U >::isSigned && sizeof(T) < 4 && sizeof(U) < 8 ) ) ? ComparisonMethod_CastInt : + ( ( IntTraits< T >::isSigned && sizeof(U) < 8 ) || + ( IntTraits< U >::isSigned && sizeof(T) < 8 ) ) ? ComparisonMethod_CastInt64 : + ( !IntTraits< T >::isSigned ) ? ComparisonMethod_UnsignedT : + ComparisonMethod_UnsignedU ) +#endif + }; +}; + +template class EqualityTest; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_Ok > +{ +public: + static bool IsEquals( const T t, const U u ) throw() { return ( t == u ); } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_CastInt > +{ +public: + static bool IsEquals( const T t, const U u ) throw() { return ( (int)t == (int)u ); } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_CastInt64 > +{ +public: + static bool IsEquals( const T t, const U u ) throw() { return ( (__int64)t == (__int64)u ); } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_UnsignedT > +{ +public: + static bool IsEquals( const T t, const U u ) throw() + { + //one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( u < 0 ) + return false; + + //else safe to cast to type T + return ( t == (T)u ); + } +}; + +template < typename T, typename U > class EqualityTest< T, U, ComparisonMethod_UnsignedU> +{ +public: + static bool IsEquals( const T t, const U u ) throw() + { + //one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( t < 0 ) + return false; + + //else safe to cast to type U + return ( (U)t == u ); + } +}; + +template class GreaterThanTest; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_Ok > +{ +public: + static bool GreaterThan( const T t, const U u ) throw() { return ( t > u ); } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_CastInt > +{ +public: + static bool GreaterThan( const T t, const U u ) throw() { return ( (int)t > (int)u ); } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_CastInt64 > +{ +public: + static bool GreaterThan( const T t, const U u ) throw() { return ( (__int64)t > (__int64)u ); } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_UnsignedT > +{ +public: + static bool GreaterThan( const T t, const U u ) throw() + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( u < 0 ) + return true; + + // else safe to cast to type T + return ( t > (T)u ); + } +}; + +template < typename T, typename U > class GreaterThanTest< T, U, ComparisonMethod_UnsignedU > +{ +public: + static bool GreaterThan( const T t, const U u ) throw() + { + // one operand is 32 or 64-bit unsigned, and the other is signed and the same size or smaller + if( t < 0 ) + return false; + + // else safe to cast to type U + return ( (U)t > u ); + } +}; + +// Modulus is simpler than comparison, but follows much the same logic +// using this set of functions, it can't fail except in a div 0 situation +template class ModulusHelper; + +template class ModulusHelper +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) throw() + { + if(u == 0) + return SafeIntDivideByZero; + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + // Some compilers don't notice that this only compiles when u is signed + // Add cast to make them happy + if( u == (U)-1 ) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)(t % u); + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) + { + if(u == 0) + E::SafeIntOnDivZero(); + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return; + } + } + + result = (T)(t % u); + } +}; + +template class ModulusHelper +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) throw() + { + if(u == 0) + return SafeIntDivideByZero; + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)(t % u); + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) + { + if(u == 0) + E::SafeIntOnDivZero(); + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return; + } + } + + result = (T)(t % u); + } +}; + +template < typename T, typename U > class ModulusHelper< T, U, ComparisonMethod_CastInt64> +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) throw() + { + if(u == 0) + return SafeIntDivideByZero; + + //trap corner case + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return SafeIntNoError; + } + } + + result = (T)((__int64)t % (__int64)u); + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) + { + if(u == 0) + E::SafeIntOnDivZero(); + + if( CompileConst< IntTraits< U >::isSigned >::Value() ) + { + if( u == (U)-1 ) + { + result = 0; + return; + } + } + + result = (T)((__int64)t % (__int64)u); + } +}; + +// T is unsigned __int64, U is any signed int +template < typename T, typename U > class ModulusHelper< T, U, ComparisonMethod_UnsignedT> +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) throw() + { + if(u == 0) + return SafeIntDivideByZero; + + // u could be negative - if so, need to convert to positive + // casts below are always safe due to the way modulus works + if(u < 0) + result = (T)(t % AbsValueHelper< U, GetAbsMethod< U >::method >::Abs(u)); + else + result = (T)(t % u); + + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) + { + if(u == 0) + E::SafeIntOnDivZero(); + + // u could be negative - if so, need to convert to positive + if(u < 0) + result = (T)(t % AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( u )); + else + result = (T)(t % u); + } +}; + +// U is unsigned __int64, T any signed int +template < typename T, typename U > class ModulusHelper< T, U, ComparisonMethod_UnsignedU> +{ +public: + static SafeIntError Modulus( const T& t, const U& u, T& result ) throw() + { + if(u == 0) + return SafeIntDivideByZero; + + //t could be negative - if so, need to convert to positive + if(t < 0) + result = (T)( ~( AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( t ) % u ) + 1 ); + else + result = (T)((T)t % u); + + return SafeIntNoError; + } + + template < typename E > + static void ModulusThrow( const T& t, const U& u, T& result ) + { + if(u == 0) + E::SafeIntOnDivZero(); + + //t could be negative - if so, need to convert to positive + if(t < 0) + result = (T)( ~( AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( t ) % u ) + 1); + else + result = (T)( (T)t % u ); + } +}; + +//core logic to determine method to check multiplication +enum MultiplicationState +{ + MultiplicationState_CastInt = 0, // One or both signed, smaller than 32-bit + MultiplicationState_CastInt64, // One or both signed, smaller than 64-bit + MultiplicationState_CastUint, // Both are unsigned, smaller than 32-bit + MultiplicationState_CastUint64, // Both are unsigned, both 32-bit or smaller + MultiplicationState_Uint64Uint, // Both are unsigned, lhs 64-bit, rhs 32-bit or smaller + MultiplicationState_Uint64Uint64, // Both are unsigned int64 + MultiplicationState_Uint64Int, // lhs is unsigned int64, rhs int32 + MultiplicationState_Uint64Int64, // lhs is unsigned int64, rhs signed int64 + MultiplicationState_UintUint64, // Both are unsigned, lhs 32-bit or smaller, rhs 64-bit + MultiplicationState_UintInt64, // lhs unsigned 32-bit or less, rhs int64 + MultiplicationState_Int64Uint, // lhs int64, rhs unsigned int32 + MultiplicationState_Int64Int64, // lhs int64, rhs int64 + MultiplicationState_Int64Int, // lhs int64, rhs int32 + MultiplicationState_IntUint64, // lhs int, rhs unsigned int64 + MultiplicationState_IntInt64, // lhs int, rhs int64 + MultiplicationState_Int64Uint64, // lhs int64, rhs uint64 + MultiplicationState_Error +}; + +template < typename T, typename U > +class MultiplicationMethod +{ +public: + enum + { + // unsigned-unsigned + method = (IntRegion< T,U >::IntZone_UintLT32_UintLT32 ? MultiplicationState_CastUint : + (IntRegion< T,U >::IntZone_Uint32_UintLT64 || + IntRegion< T,U >::IntZone_UintLT32_Uint32) ? MultiplicationState_CastUint64 : + SafeIntCompare< T,U >::isBothUnsigned && + IntTraits< T >::isUint64 && IntTraits< U >::isUint64 ? MultiplicationState_Uint64Uint64 : + (IntRegion< T,U >::IntZone_Uint64_Uint) ? MultiplicationState_Uint64Uint : + (IntRegion< T,U >::IntZone_UintLT64_Uint64) ? MultiplicationState_UintUint64 : + // unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? MultiplicationState_CastInt : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? MultiplicationState_CastInt64 : + (IntRegion< T,U >::IntZone_Uint64_Int) ? MultiplicationState_Uint64Int : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? MultiplicationState_UintInt64 : + (IntRegion< T,U >::IntZone_Uint64_Int64) ? MultiplicationState_Uint64Int64 : + // signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? MultiplicationState_CastInt : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? MultiplicationState_CastInt64 : + (IntRegion< T,U >::IntZone_Int64_Int64) ? MultiplicationState_Int64Int64 : + (IntRegion< T,U >::IntZone_Int64_Int) ? MultiplicationState_Int64Int : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? MultiplicationState_IntInt64 : + // signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? MultiplicationState_CastInt : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? MultiplicationState_CastInt64 : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? MultiplicationState_Int64Uint : + (IntRegion< T,U >::IntZone_Int_Uint64) ? MultiplicationState_IntUint64 : + (IntRegion< T,U >::IntZone_Int64_Uint64 ? MultiplicationState_Int64Uint64 : + MultiplicationState_Error ) ) + }; +}; + +template class MultiplicationHelper; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastInt> +{ +public: + //accepts signed, both less than 32-bit + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + int tmp = t * u; + + if( tmp > IntTraits< T >::maxInt || tmp < IntTraits< T >::minInt ) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) + { + int tmp = t * u; + + if( tmp > IntTraits< T >::maxInt || tmp < IntTraits< T >::minInt ) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastUint > +{ +public: + //accepts unsigned, both less than 32-bit + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + unsigned int tmp = (unsigned int)(t * u); + + if( tmp > IntTraits< T >::maxInt ) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) + { + unsigned int tmp = (unsigned int)( t * u ); + + if( tmp > IntTraits< T >::maxInt ) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastInt64> +{ +public: + //mixed signed or both signed where at least one argument is 32-bit, and both a 32-bit or less + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + __int64 tmp = (__int64)t * (__int64)u; + + if(tmp > (__int64)IntTraits< T >::maxInt || tmp < (__int64)IntTraits< T >::minInt) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) + { + __int64 tmp = (__int64)t * (__int64)u; + + if(tmp > (__int64)IntTraits< T >::maxInt || tmp < (__int64)IntTraits< T >::minInt) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_CastUint64> +{ +public: + //both unsigned where at least one argument is 32-bit, and both are 32-bit or less + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + unsigned __int64 tmp = (unsigned __int64)t * (unsigned __int64)u; + + if(tmp > (unsigned __int64)IntTraits< T >::maxInt) + return false; + + ret = (T)tmp; + return true; + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) + { + unsigned __int64 tmp = (unsigned __int64)t * (unsigned __int64)u; + + if(tmp > (unsigned __int64)IntTraits< T >::maxInt) + E::SafeIntOnOverflow(); + + ret = (T)tmp; + } +}; + +// T = left arg and return type +// U = right arg +template < typename T, typename U > class LargeIntRegMultiply; + +#if SAFEINT_USE_INTRINSICS +// As usual, unsigned is easy +inline bool IntrinsicMultiplyUint64( const unsigned __int64& a, const unsigned __int64& b, unsigned __int64* pRet ) +{ + unsigned __int64 ulHigh = 0; + *pRet = _umul128(a , b, &ulHigh); + return ulHigh == 0; +} + +// Signed, is not so easy +inline bool IntrinsicMultiplyInt64( const signed __int64& a, const signed __int64& b, signed __int64* pRet ) +{ + __int64 llHigh = 0; + *pRet = _mul128(a , b, &llHigh); + + // Now we need to figure out what we expect + // If llHigh is 0, then treat *pRet as unsigned + // If llHigh is < 0, then treat *pRet as signed + + if( (a ^ b) < 0 ) + { + // Negative result expected + if( llHigh == -1 && *pRet < 0 || + llHigh == 0 && *pRet == 0 ) + { + // Everything is within range + return true; + } + } + else + { + // Result should be positive + // Check for overflow + if( llHigh == 0 && (unsigned __int64)*pRet <= IntTraits< signed __int64 >::maxInt ) + return true; + } + return false; +} + +#endif + +template<> class LargeIntRegMultiply< unsigned __int64, unsigned __int64 > +{ +public: + static bool RegMultiply( const unsigned __int64& a, const unsigned __int64& b, unsigned __int64* pRet ) throw() + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, b, pRet ); +#else + unsigned __int32 aHigh, aLow, bHigh, bLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // Note - same approach applies for 128 bit math on a 64-bit system + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(aHigh == 0) + { + if(bHigh != 0) + { + *pRet = (unsigned __int64)aLow * (unsigned __int64)bHigh; + } + } + else if(bHigh == 0) + { + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)bLow; + } + } + else + { + return false; + } + + if(*pRet != 0) + { + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + return false; + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)bLow; + *pRet += tmp; + + if(*pRet < tmp) + return false; + + return true; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)bLow; + return true; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, const unsigned __int64& b, unsigned __int64* pRet ) + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, b, pRet ) ) + E::SafeIntOnOverflow(); +#else + unsigned __int32 aHigh, aLow, bHigh, bLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // Note - same approach applies for 128 bit math on a 64-bit system + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(aHigh == 0) + { + if(bHigh != 0) + { + *pRet = (unsigned __int64)aLow * (unsigned __int64)bHigh; + } + } + else if(bHigh == 0) + { + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)bLow; + } + } + else + { + E::SafeIntOnOverflow(); + } + + if(*pRet != 0) + { + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + E::SafeIntOnOverflow(); + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)bLow; + *pRet += tmp; + + if(*pRet < tmp) + E::SafeIntOnOverflow(); + + return; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)bLow; +#endif + } +}; + +template<> class LargeIntRegMultiply< unsigned __int64, unsigned __int32 > +{ +public: + static bool RegMultiply( const unsigned __int64& a, unsigned __int32 b, unsigned __int64* pRet ) throw() + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ); +#else + unsigned __int32 aHigh, aLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * b + // => (aHigh * b * 2^32) + (aLow * b) + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + + *pRet = 0; + + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)b; + + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + return false; + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)b; + *pRet += tmp; + + if(*pRet < tmp) + return false; + + return true; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)b; + return true; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, unsigned __int32 b, unsigned __int64* pRet ) + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + unsigned __int32 aHigh, aLow; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * b + // => (aHigh * b * 2^32) + (aLow * b) + + aHigh = (unsigned __int32)(a >> 32); + aLow = (unsigned __int32)a; + + *pRet = 0; + + if(aHigh != 0) + { + *pRet = (unsigned __int64)aHigh * (unsigned __int64)b; + + unsigned __int64 tmp; + + if((unsigned __int32)(*pRet >> 32) != 0) + E::SafeIntOnOverflow(); + + *pRet <<= 32; + tmp = (unsigned __int64)aLow * (unsigned __int64)b; + *pRet += tmp; + + if(*pRet < tmp) + E::SafeIntOnOverflow(); + + return; + } + + *pRet = (unsigned __int64)aLow * (unsigned __int64)b; + return; +#endif + } +}; + +template<> class LargeIntRegMultiply< unsigned __int64, signed __int32 > +{ +public: + // Intrinsic not needed + static bool RegMultiply( const unsigned __int64& a, signed __int32 b, unsigned __int64* pRet ) throw() + { + if( b < 0 && a != 0 ) + return false; + +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ); +#else + return LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply(a, (unsigned __int32)b, pRet); +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, signed __int32 b, unsigned __int64* pRet ) + { + if( b < 0 && a != 0 ) + E::SafeIntOnOverflow(); + +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( a, (unsigned __int32)b, pRet ); +#endif + } +}; + +template<> class LargeIntRegMultiply< unsigned __int64, signed __int64 > +{ +public: + static bool RegMultiply( const unsigned __int64& a, signed __int64 b, unsigned __int64* pRet ) throw() + { + if( b < 0 && a != 0 ) + return false; + +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ); +#else + return LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply(a, (unsigned __int64)b, pRet); +#endif + } + + template < typename E > + static void RegMultiplyThrow( const unsigned __int64& a, signed __int64 b, unsigned __int64* pRet ) + { + if( b < 0 && a != 0 ) + E::SafeIntOnOverflow(); + +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyUint64( a, (unsigned __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::template RegMultiplyThrow< E >( a, (unsigned __int64)b, pRet ); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int32, unsigned __int64 > +{ +public: + // Devolves into ordinary 64-bit calculation + static bool RegMultiply( signed __int32 a, const unsigned __int64& b, signed __int32* pRet ) throw() + { + unsigned __int32 bHigh, bLow; + bool fIsNegative = false; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + // aHigh == 0 implies: + // ( aLow * bHigh * 2^32 ) + ( aLow + bLow ) + // If the first part is != 0, fail + + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(bHigh != 0 && a != 0) + return false; + + if( a < 0 ) + { + + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + fIsNegative = true; + } + + unsigned __int64 tmp = (unsigned __int32)a * (unsigned __int64)bLow; + + if( !fIsNegative ) + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return true; + } + } + else + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt+1 ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return true; + } + } + + return false; + } + + template < typename E > + static void RegMultiplyThrow( signed __int32 a, const unsigned __int64& b, signed __int32* pRet ) + { + unsigned __int32 bHigh, bLow; + bool fIsNegative = false; + + // Consider that a*b can be broken up into: + // (aHigh * 2^32 + aLow) * (bHigh * 2^32 + bLow) + // => (aHigh * bHigh * 2^64) + (aLow * bHigh * 2^32) + (aHigh * bLow * 2^32) + (aLow * bLow) + + bHigh = (unsigned __int32)(b >> 32); + bLow = (unsigned __int32)b; + + *pRet = 0; + + if(bHigh != 0 && a != 0) + E::SafeIntOnOverflow(); + + if( a < 0 ) + { + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + fIsNegative = true; + } + + unsigned __int64 tmp = (unsigned __int32)a * (unsigned __int64)bLow; + + if( !fIsNegative ) + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return; + } + } + else + { + if( tmp <= (unsigned __int64)IntTraits< signed __int32 >::maxInt+1 ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template<> class LargeIntRegMultiply< unsigned __int32, unsigned __int64 > +{ +public: + // Becomes ordinary 64-bit multiplication, intrinsic not needed + static bool RegMultiply( unsigned __int32 a, const unsigned __int64& b, unsigned __int32* pRet ) throw() + { + // Consider that a*b can be broken up into: + // (bHigh * 2^32 + bLow) * a + // => (bHigh * a * 2^32) + (bLow * a) + // In this case, the result must fit into 32-bits + // If bHigh != 0 && a != 0, immediate error. + + if( (unsigned __int32)(b >> 32) != 0 && a != 0 ) + return false; + + unsigned __int64 tmp = b * (unsigned __int64)a; + + if( (unsigned __int32)(tmp >> 32) != 0 ) // overflow + return false; + + *pRet = (unsigned __int32)tmp; + return true; + } + + template < typename E > + static void RegMultiplyThrow( unsigned __int32 a, const unsigned __int64& b, unsigned __int32* pRet ) + { + if( (unsigned __int32)(b >> 32) != 0 && a != 0 ) + E::SafeIntOnOverflow(); + + unsigned __int64 tmp = b * (unsigned __int64)a; + + if( (unsigned __int32)(tmp >> 32) != 0 ) // overflow + E::SafeIntOnOverflow(); + + *pRet = (unsigned __int32)tmp; + } +}; + +template<> class LargeIntRegMultiply< unsigned __int32, signed __int64 > +{ +public: + static bool RegMultiply( unsigned __int32 a, const signed __int64& b, unsigned __int32* pRet ) throw() + { + if( b < 0 && a != 0 ) + return false; + return LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::RegMultiply( a, (unsigned __int64)b, pRet ); + } + + template < typename E > + static void RegMultiplyThrow( unsigned __int32 a, const signed __int64& b, unsigned __int32* pRet ) + { + if( b < 0 && a != 0 ) + E::SafeIntOnOverflow(); + + LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::template RegMultiplyThrow< E >( a, (unsigned __int64)b, pRet ); + } +}; + +template<> class LargeIntRegMultiply< signed __int64, signed __int64 > +{ +public: + static bool RegMultiply( const signed __int64& a, const signed __int64& b, signed __int64* pRet ) throw() + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64( a, b, pRet ); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( (unsigned __int64)a1, (unsigned __int64)b1, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const signed __int64& a, const signed __int64& b, signed __int64* pRet ) + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyInt64( a, b, pRet ) ) + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::template RegMultiplyThrow< E >( (unsigned __int64)a1, (unsigned __int64)b1, &tmp ); + + // The unsigned multiplication didn't overflow or we'd be in the exception handler + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int64, unsigned __int32 > +{ +public: + static bool RegMultiply( const signed __int64& a, unsigned __int32 b, signed __int64* pRet ) throw() + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ); +#else + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply( (unsigned __int64)a1, b, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( const signed __int64& a, unsigned __int32 b, signed __int64* pRet ) + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( (unsigned __int64)a1, b, &tmp ); + + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int64, signed __int32 > +{ +public: + static bool RegMultiply( const signed __int64& a, signed __int32 b, signed __int64* pRet ) throw() + { +#if SAFEINT_USE_INTRINSICS + return IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + __int64 b1 = b; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply( (unsigned __int64)a1, (unsigned __int32)b1, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( signed __int64 a, signed __int32 b, signed __int64* pRet ) + { +#if SAFEINT_USE_INTRINSICS + if( !IntrinsicMultiplyInt64( a, (signed __int64)b, pRet ) ) + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int64 tmp; + + if( a < 0 ) + { + aNegative = true; + a = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a); + } + + if( b < 0 ) + { + bNegative = true; + b = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(b); + } + + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( (unsigned __int64)a, (unsigned __int32)b, &tmp ); + + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int32, signed __int64 > +{ +public: + static bool RegMultiply( signed __int32 a, const signed __int64& b, signed __int32* pRet ) throw() + { +#if SAFEINT_USE_INTRINSICS + __int64 tmp; + + if( IntrinsicMultiplyInt64( a, b, &tmp ) ) + { + if( tmp > IntTraits< signed __int32 >::maxInt || + tmp < IntTraits< signed __int32 >::minInt ) + { + return false; + } + + *pRet = (__int32)tmp; + return true; + } + return false; +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int32 tmp; + __int64 b1 = b; + + if( a < 0 ) + { + aNegative = true; + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + } + + if( b1 < 0 ) + { + bNegative = true; + b1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b1); + } + + if( LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::RegMultiply( (unsigned __int32)a, (unsigned __int64)b1, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::minInt ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return true; + } + } + } + + return false; +#endif + } + + template < typename E > + static void RegMultiplyThrow( signed __int32 a, const signed __int64& b, signed __int32* pRet ) + { +#if SAFEINT_USE_INTRINSICS + __int64 tmp; + + if( IntrinsicMultiplyInt64( a, b, &tmp ) ) + { + if( tmp > IntTraits< signed __int32 >::maxInt || + tmp < IntTraits< signed __int32 >::minInt ) + { + E::SafeIntOnOverflow(); + } + + *pRet = (__int32)tmp; + return; + } + E::SafeIntOnOverflow(); +#else + bool aNegative = false; + bool bNegative = false; + + unsigned __int32 tmp; + signed __int64 b2 = b; + + if( a < 0 ) + { + aNegative = true; + a = (signed __int32)AbsValueHelper< signed __int32, GetAbsMethod< signed __int32 >::method >::Abs(a); + } + + if( b < 0 ) + { + bNegative = true; + b2 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(b2); + } + + LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::template RegMultiplyThrow< E >( (unsigned __int32)a, (unsigned __int64)b2, &tmp ); + + // The unsigned multiplication didn't overflow + if( aNegative ^ bNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::minInt ) + { + *pRet = SignedNegation< signed __int32 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int32)IntTraits< signed __int32 >::maxInt ) + { + *pRet = (signed __int32)tmp; + return; + } + } + + E::SafeIntOnOverflow(); +#endif + } +}; + +template<> class LargeIntRegMultiply< signed __int64, unsigned __int64 > +{ +public: + // Leave this one as-is - will call unsigned intrinsic internally + static bool RegMultiply( const signed __int64& a, const unsigned __int64& b, signed __int64* pRet ) throw() + { + bool aNegative = false; + + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( (unsigned __int64)a1, (unsigned __int64)b, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return true; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return true; + } + } + } + + return false; + } + + template < typename E > + static void RegMultiplyThrow( const signed __int64& a, const unsigned __int64& b, signed __int64* pRet ) + { + bool aNegative = false; + unsigned __int64 tmp; + __int64 a1 = a; + + if( a1 < 0 ) + { + aNegative = true; + a1 = (signed __int64)AbsValueHelper< signed __int64, GetAbsMethod< signed __int64 >::method >::Abs(a1); + } + + if( LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( (unsigned __int64)a1, (unsigned __int64)b, &tmp ) ) + { + // The unsigned multiplication didn't overflow + if( aNegative ) + { + // Result must be negative + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::minInt ) + { + *pRet = SignedNegation< signed __int64 >::Value( tmp ); + return; + } + } + else + { + // Result must be positive + if( tmp <= (unsigned __int64)IntTraits< signed __int64 >::maxInt ) + { + *pRet = (signed __int64)tmp; + return; + } + } + } + + E::SafeIntOnOverflow(); + } +}; + +// In all of the following functions where LargeIntRegMultiply methods are called, +// we need to properly transition types. The methods need __int64, __int32, etc. +// but the variables being passed to us could be long long, long int, or long, depending on +// the compiler. Microsoft compiler knows that long long is the same type as __int64, but gcc doesn't + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Uint64 > +{ +public: + // T, U are unsigned __int64 + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isUint64 ); + unsigned __int64 t1 = t; + unsigned __int64 u1 = u; + return LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::RegMultiply( t1, u1, reinterpret_cast(&ret) ); + } + + template < typename E > + static void MultiplyThrow(const unsigned __int64& t, const unsigned __int64& u, T& ret) + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isUint64 ); + unsigned __int64 t1 = t; + unsigned __int64 u1 = u; + LargeIntRegMultiply< unsigned __int64, unsigned __int64 >::template RegMultiplyThrow< E >( t1, u1, reinterpret_cast(&ret) ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Uint > +{ +public: + // T is unsigned __int64 + // U is any unsigned int 32-bit or less + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + return LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::RegMultiply( t1, (unsigned __int32)u, reinterpret_cast(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + LargeIntRegMultiply< unsigned __int64, unsigned __int32 >::template RegMultiplyThrow< E >( t1, (unsigned __int32)u, reinterpret_cast(&ret) ); + } +}; + +// converse of the previous function +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_UintUint64 > +{ +public: + // T is any unsigned int up to 32-bit + // U is unsigned __int64 + static bool Multiply(const T& t, const U& u, T& ret) throw() + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + unsigned __int32 tmp; + + if( LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::RegMultiply( t, u1, &tmp ) && + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::Cast(tmp, ret) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + unsigned __int32 tmp; + + LargeIntRegMultiply< unsigned __int32, unsigned __int64 >::template RegMultiplyThrow< E >( t, u1, &tmp ); + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::template CastThrow< E >(tmp, ret); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Int > +{ +public: + // T is unsigned __int64 + // U is any signed int, up to 64-bit + static bool Multiply(const T& t, const U& u, T& ret) throw() + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + return LargeIntRegMultiply< unsigned __int64, signed __int32 >::RegMultiply(t1, (signed __int32)u, reinterpret_cast< unsigned __int64* >(&ret)); + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 t1 = t; + LargeIntRegMultiply< unsigned __int64, signed __int32 >::template RegMultiplyThrow< E >(t1, (signed __int32)u, reinterpret_cast< unsigned __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Uint64Int64 > +{ +public: + // T is unsigned __int64 + // U is __int64 + static bool Multiply(const T& t, const U& u, T& ret) throw() + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isInt64 ); + unsigned __int64 t1 = t; + __int64 u1 = u; + return LargeIntRegMultiply< unsigned __int64, __int64 >::RegMultiply(t1, u1, reinterpret_cast< unsigned __int64* >(&ret)); + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) + { + C_ASSERT( IntTraits::isUint64 && IntTraits::isInt64 ); + unsigned __int64 t1 = t; + __int64 u1 = u; + LargeIntRegMultiply< unsigned __int64, __int64 >::template RegMultiplyThrow< E >(t1, u1, reinterpret_cast< unsigned __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_UintInt64 > +{ +public: + // T is unsigned up to 32-bit + // U is __int64 + static bool Multiply(const T& t, const U& u, T& ret) throw() + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + unsigned __int32 tmp; + + if( LargeIntRegMultiply< unsigned __int32, __int64 >::RegMultiply( (unsigned __int32)t, u1, &tmp ) && + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::Cast(tmp, ret) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(const T& t, const U& u, T& ret) + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + unsigned __int32 tmp; + + LargeIntRegMultiply< unsigned __int32, __int64 >::template RegMultiplyThrow< E >( (unsigned __int32)t, u1, &tmp ); + SafeCastHelper< T, unsigned __int32, GetCastMethod< T, unsigned __int32 >::method >::template CastThrow< E >(tmp, ret); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Uint > +{ +public: + // T is __int64 + // U is unsigned up to 32-bit + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + return LargeIntRegMultiply< __int64, unsigned __int32 >::RegMultiply( t1, (unsigned __int32)u, reinterpret_cast< __int64* >(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + LargeIntRegMultiply< __int64, unsigned __int32 >::template RegMultiplyThrow< E >( t1, (unsigned __int32)u, reinterpret_cast< __int64* >(&ret) ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Int64 > +{ +public: + // T, U are __int64 + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isInt64 ); + __int64 t1 = t; + __int64 u1 = u; + return LargeIntRegMultiply< __int64, __int64 >::RegMultiply( t1, u1, reinterpret_cast< __int64* >(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const T& t, const U& u, T& ret ) + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isInt64 ); + __int64 t1 = t; + __int64 u1 = u; + LargeIntRegMultiply< __int64, __int64 >::template RegMultiplyThrow< E >( t1, u1, reinterpret_cast< __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Int > +{ +public: + // T is __int64 + // U is signed up to 32-bit + static bool Multiply( const T& t, U u, T& ret ) throw() + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + return LargeIntRegMultiply< __int64, __int32 >::RegMultiply( t1, (__int32)u, reinterpret_cast< __int64* >(&ret)); + } + + template < typename E > + static void MultiplyThrow( const __int64& t, U u, T& ret ) + { + C_ASSERT( IntTraits::isInt64 ); + __int64 t1 = t; + LargeIntRegMultiply< __int64, __int32 >::template RegMultiplyThrow< E >(t1, (__int32)u, reinterpret_cast< __int64* >(&ret)); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_IntUint64 > +{ +public: + // T is signed up to 32-bit + // U is unsigned __int64 + static bool Multiply(T t, const U& u, T& ret) throw() + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + __int32 tmp; + + if( LargeIntRegMultiply< __int32, unsigned __int64 >::RegMultiply( (__int32)t, u1, &tmp ) && + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, ret ) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(T t, const unsigned __int64& u, T& ret) + { + C_ASSERT( IntTraits::isUint64 ); + unsigned __int64 u1 = u; + __int32 tmp; + + LargeIntRegMultiply< __int32, unsigned __int64 >::template RegMultiplyThrow< E >( (__int32)t, u1, &tmp ); + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, ret ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_Int64Uint64> +{ +public: + // T is __int64 + // U is unsigned __int64 + static bool Multiply( const T& t, const U& u, T& ret ) throw() + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isUint64 ); + __int64 t1 = t; + unsigned __int64 u1 = u; + return LargeIntRegMultiply< __int64, unsigned __int64 >::RegMultiply( t1, u1, reinterpret_cast< __int64* >(&ret) ); + } + + template < typename E > + static void MultiplyThrow( const __int64& t, const unsigned __int64& u, T& ret ) + { + C_ASSERT( IntTraits::isInt64 && IntTraits::isUint64 ); + __int64 t1 = t; + unsigned __int64 u1 = u; + LargeIntRegMultiply< __int64, unsigned __int64 >::template RegMultiplyThrow< E >( t1, u1, reinterpret_cast< __int64* >(&ret) ); + } +}; + +template < typename T, typename U > class MultiplicationHelper< T, U, MultiplicationState_IntInt64> +{ +public: + // T is signed, up to 32-bit + // U is __int64 + static bool Multiply( T t, const U& u, T& ret ) throw() + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + __int32 tmp; + + if( LargeIntRegMultiply< __int32, __int64 >::RegMultiply( (__int32)t, u1, &tmp ) && + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, ret ) ) + { + return true; + } + + return false; + } + + template < typename E > + static void MultiplyThrow(T t, const U& u, T& ret) + { + C_ASSERT( IntTraits::isInt64 ); + __int64 u1 = u; + __int32 tmp; + + LargeIntRegMultiply< __int32, __int64 >::template RegMultiplyThrow< E >( (__int32)t, u1, &tmp ); + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, ret ); + } +}; + +enum DivisionState +{ + DivisionState_OK, + DivisionState_UnsignedSigned, + DivisionState_SignedUnsigned32, + DivisionState_SignedUnsigned64, + DivisionState_SignedUnsigned, + DivisionState_SignedSigned +}; + +template < typename T, typename U > class DivisionMethod +{ +public: + enum + { + method = (SafeIntCompare< T, U >::isBothUnsigned ? DivisionState_OK : + (!IntTraits< T >::isSigned && IntTraits< U >::isSigned) ? DivisionState_UnsignedSigned : + (IntTraits< T >::isSigned && + IntTraits< U >::isUint32 && + IntTraits< T >::isLT64Bit) ? DivisionState_SignedUnsigned32 : + (IntTraits< T >::isSigned && IntTraits< U >::isUint64) ? DivisionState_SignedUnsigned64 : + (IntTraits< T >::isSigned && !IntTraits< U >::isSigned) ? DivisionState_SignedUnsigned : + DivisionState_SignedSigned) + }; +}; + +template < typename T, typename U, int state > class DivisionHelper; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_OK > +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) throw() + { + if( u == 0 ) + return SafeIntDivideByZero; + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + result = (T)( t/u ); + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) + { + if( u == 0 ) + E::SafeIntOnDivZero(); + + if( t == 0 ) + { + result = 0; + return; + } + + result = (T)( t/u ); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_UnsignedSigned> +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) throw() + { + + if( u == 0 ) + return SafeIntDivideByZero; + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + if( u > 0 ) + { + result = (T)( t/u ); + return SafeIntNoError; + } + + // it is always an error to try and divide an unsigned number by a negative signed number + // unless u is bigger than t + if( AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( u ) > t ) + { + result = 0; + return SafeIntNoError; + } + + return SafeIntArithmeticOverflow; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) + { + + if( u == 0 ) + E::SafeIntOnDivZero(); + + if( t == 0 ) + { + result = 0; + return; + } + + if( u > 0 ) + { + result = (T)( t/u ); + return; + } + + // it is always an error to try and divide an unsigned number by a negative signed number + // unless u is bigger than t + if( AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( u ) > t ) + { + result = 0; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedUnsigned32 > +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) throw() + { + if( u == 0 ) + return SafeIntDivideByZero; + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + // Test for t > 0 + // If t < 0, must explicitly upcast, or implicit upcast to ulong will cause errors + // As it turns out, 32-bit division is about twice as fast, which justifies the extra conditional + + if( t > 0 ) + result = (T)( t/u ); + else + result = (T)( (__int64)t/(__int64)u ); + + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) + { + if( u == 0 ) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + // Test for t > 0 + // If t < 0, must explicitly upcast, or implicit upcast to ulong will cause errors + // As it turns out, 32-bit division is about twice as fast, which justifies the extra conditional + + if( t > 0 ) + result = (T)( t/u ); + else + result = (T)( (__int64)t/(__int64)u ); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedUnsigned64 > +{ +public: + static SafeIntError Divide( const T& t, const unsigned __int64& u, T& result ) throw() + { + C_ASSERT( IntTraits< U >::isUint64 ); + + if( u == 0 ) + { + return SafeIntDivideByZero; + } + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + if( u <= (unsigned __int64)IntTraits< T >::maxInt ) + { + // Else u can safely be cast to T + if( CompileConst< sizeof( T ) < sizeof( __int64 )>::Value() ) + result = (T)( (int)t/(int)u ); + else + result = (T)((__int64)t/(__int64)u); + } + else // Corner case + if( t == IntTraits< T >::minInt && u == (unsigned __int64)IntTraits< T >::minInt ) + { + // Min int divided by it's own magnitude is -1 + result = -1; + } + else + { + result = 0; + } + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const unsigned __int64& u, T& result ) + { + C_ASSERT( IntTraits< U >::isUint64 ); + + if( u == 0 ) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + if( u <= (unsigned __int64)IntTraits< T >::maxInt ) + { + // Else u can safely be cast to T + if( CompileConst< sizeof( T ) < sizeof( __int64 ) >::Value() ) + result = (T)( (int)t/(int)u ); + else + result = (T)((__int64)t/(__int64)u); + } + else // Corner case + if( t == IntTraits< T >::minInt && u == (unsigned __int64)IntTraits< T >::minInt ) + { + // Min int divided by it's own magnitude is -1 + result = -1; + } + else + { + result = 0; + } + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedUnsigned> +{ +public: + // T is any signed, U is unsigned and smaller than 32-bit + // In this case, standard operator casting is correct + static SafeIntError Divide( const T& t, const U& u, T& result ) throw() + { + if( u == 0 ) + { + return SafeIntDivideByZero; + } + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + result = (T)( t/u ); + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) + { + if( u == 0 ) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + result = (T)( t/u ); + } +}; + +template < typename T, typename U > class DivisionHelper< T, U, DivisionState_SignedSigned> +{ +public: + static SafeIntError Divide( const T& t, const U& u, T& result ) throw() + { + if( u == 0 ) + { + return SafeIntDivideByZero; + } + + if( t == 0 ) + { + result = 0; + return SafeIntNoError; + } + + // Must test for corner case + if( t == IntTraits< T >::minInt && u == (U)-1 ) + return SafeIntArithmeticOverflow; + + result = (T)( t/u ); + return SafeIntNoError; + } + + template < typename E > + static void DivideThrow( const T& t, const U& u, T& result ) + { + if(u == 0) + { + E::SafeIntOnDivZero(); + } + + if( t == 0 ) + { + result = 0; + return; + } + + // Must test for corner case + if( t == IntTraits< T >::minInt && u == (U)-1 ) + E::SafeIntOnOverflow(); + + result = (T)( t/u ); + } +}; + +enum AdditionState +{ + AdditionState_CastIntCheckMax, + AdditionState_CastUintCheckOverflow, + AdditionState_CastUintCheckOverflowMax, + AdditionState_CastUint64CheckOverflow, + AdditionState_CastUint64CheckOverflowMax, + AdditionState_CastIntCheckMinMax, + AdditionState_CastInt64CheckMinMax, + AdditionState_CastInt64CheckMax, + AdditionState_CastUint64CheckMinMax, + AdditionState_CastUint64CheckMinMax2, + AdditionState_CastInt64CheckOverflow, + AdditionState_CastInt64CheckOverflowMinMax, + AdditionState_CastInt64CheckOverflowMax, + AdditionState_ManualCheckInt64Uint64, + AdditionState_ManualCheck, + AdditionState_Error +}; + +template< typename T, typename U > +class AdditionMethod +{ +public: + enum + { + //unsigned-unsigned + method = (IntRegion< T,U >::IntZone_UintLT32_UintLT32 ? AdditionState_CastIntCheckMax : + (IntRegion< T,U >::IntZone_Uint32_UintLT64) ? AdditionState_CastUintCheckOverflow : + (IntRegion< T,U >::IntZone_UintLT32_Uint32) ? AdditionState_CastUintCheckOverflowMax : + (IntRegion< T,U >::IntZone_Uint64_Uint) ? AdditionState_CastUint64CheckOverflow : + (IntRegion< T,U >::IntZone_UintLT64_Uint64) ? AdditionState_CastUint64CheckOverflowMax : + //unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? AdditionState_CastIntCheckMinMax : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? AdditionState_CastInt64CheckMinMax : + (IntRegion< T,U >::IntZone_Uint64_Int || + IntRegion< T,U >::IntZone_Uint64_Int64) ? AdditionState_CastUint64CheckMinMax : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? AdditionState_CastUint64CheckMinMax2 : + //signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? AdditionState_CastIntCheckMinMax : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? AdditionState_CastInt64CheckMinMax : + (IntRegion< T,U >::IntZone_Int64_Int || + IntRegion< T,U >::IntZone_Int64_Int64) ? AdditionState_CastInt64CheckOverflow : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? AdditionState_CastInt64CheckOverflowMinMax : + //signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? AdditionState_CastIntCheckMax : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? AdditionState_CastInt64CheckMax : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? AdditionState_CastInt64CheckOverflowMax : + (IntRegion< T,U >::IntZone_Int64_Uint64) ? AdditionState_ManualCheckInt64Uint64 : + (IntRegion< T,U >::IntZone_Int_Uint64) ? AdditionState_ManualCheck : + AdditionState_Error) + }; +}; + +template < typename T, typename U, int method > class AdditionHelper; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastIntCheckMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + //16-bit or less unsigned addition + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + //16-bit or less unsigned addition + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUintCheckOverflow > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + //we added didn't get smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return true; + } + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + //we added didn't get smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return; + } + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUintCheckOverflowMax> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // 32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // We added and it didn't get smaller or exceed maxInt + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + //32-bit or less - both are unsigned + unsigned __int32 tmp = (unsigned __int32)lhs + (unsigned __int32)rhs; + + // We added and it didn't get smaller or exceed maxInt + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckOverflow> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if(tmp >= lhs) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if(tmp >= lhs) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckOverflowMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + //lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + //lhs unsigned __int64, rhs unsigned + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it didn't get smaller + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastIntCheckMinMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // 16-bit or less - one or both are signed + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt && tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // 16-bit or less - one or both are signed + __int32 tmp = lhs + rhs; + + if( tmp <= (__int32)IntTraits< T >::maxInt && tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckMinMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // 32-bit or less - one or both are signed + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= (__int64)IntTraits< T >::maxInt && tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // 32-bit or less - one or both are signed + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= (__int64)IntTraits< T >::maxInt && tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // 32-bit or less - lhs signed, rhs unsigned + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // 32-bit or less - lhs signed, rhs unsigned + __int64 tmp = (__int64)lhs + (__int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckMinMax > +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is unsigned __int64, rhs signed + unsigned __int64 tmp; + + if( rhs < 0 ) + { + // So we're effectively subtracting + tmp = AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if( tmp <= lhs ) + { + result = lhs - tmp; + return true; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it did not become smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is unsigned __int64, rhs signed + unsigned __int64 tmp; + + if( rhs < 0 ) + { + // So we're effectively subtracting + tmp = AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if( tmp <= lhs ) + { + result = lhs - tmp; + return; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // We added and it did not become smaller + if( tmp >= lhs ) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastUint64CheckMinMax2> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is unsigned and < 64-bit, rhs signed __int64 + if( rhs < 0 ) + { + if( lhs >= ~(unsigned __int64)( rhs ) + 1 )//negation is safe, since rhs is 64-bit + { + result = (T)( lhs + rhs ); + return true; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff + // it is not possible for the operation above to overflow, so just check max + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is unsigned and < 64-bit, rhs signed __int64 + if( rhs < 0 ) + { + if( lhs >= ~(unsigned __int64)( rhs ) + 1) //negation is safe, since rhs is 64-bit + { + result = (T)( lhs + rhs ); + return; + } + } + else + { + // now we know that rhs can be safely cast into an unsigned __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + // special case - rhs cannot be larger than 0x7fffffffffffffff, lhs cannot be larger than 0xffffffff + // it is not possible for the operation above to overflow, so just check max + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckOverflow> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is signed __int64, rhs signed + __int64 tmp = (__int64)((unsigned __int64)lhs + (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // mixed sign cannot overflow + if( rhs >= 0 && tmp < lhs ) + return false; + } + else + { + // lhs negative + if( rhs < 0 && tmp > lhs ) + return false; + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is signed __int64, rhs signed + __int64 tmp = (__int64)((unsigned __int64)lhs + (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // mixed sign cannot overflow + if( rhs >= 0 && tmp < lhs ) + E::SafeIntOnOverflow(); + } + else + { + // lhs negative + if( rhs < 0 && tmp > lhs ) + E::SafeIntOnOverflow(); + } + + result = (T)tmp; + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckOverflowMinMax> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + //rhs is signed __int64, lhs signed + __int64 tmp; + + if( AdditionHelper< __int64, __int64, AdditionState_CastInt64CheckOverflow >::Addition( (__int64)lhs, (__int64)rhs, tmp ) && + tmp <= IntTraits< T >::maxInt && + tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + //rhs is signed __int64, lhs signed + __int64 tmp; + + AdditionHelper< __int64, __int64, AdditionState_CastInt64CheckOverflow >::AdditionThrow< E >( (__int64)lhs, (__int64)rhs, tmp ); + + if( tmp <= IntTraits< T >::maxInt && + tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_CastInt64CheckOverflowMax> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + //lhs is signed __int64, rhs unsigned < 64-bit + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + if( (__int64)tmp >= lhs ) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is signed __int64, rhs unsigned < 64-bit + // Some compilers get optimization-happy, let's thwart them + + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)rhs; + + if( (__int64)tmp >= lhs ) + { + result = (T)(__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_ManualCheckInt64Uint64 > +{ +public: + static bool Addition( const __int64& lhs, const unsigned __int64& rhs, __int64& result ) throw() + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // rhs is unsigned __int64, lhs __int64 + // cast everything to unsigned, perform addition, then + // cast back for check - this is done to stop optimizers from removing the code + unsigned __int64 tmp = (unsigned __int64)lhs + rhs; + + if( (__int64)tmp >= lhs ) + { + result = (__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const __int64& lhs, const unsigned __int64& rhs, T& result ) + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // rhs is unsigned __int64, lhs __int64 + unsigned __int64 tmp = (unsigned __int64)lhs + rhs; + + if( (__int64)tmp >= lhs ) + { + result = (__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class AdditionHelper < T, U, AdditionState_ManualCheck> +{ +public: + static bool Addition( const T& lhs, const U& rhs, T& result ) throw() + { + // rhs is unsigned __int64, lhs signed, 32-bit or less + if( (unsigned __int32)( rhs >> 32 ) == 0 ) + { + // Now it just happens to work out that the standard behavior does what we want + // Adding explicit casts to show exactly what's happening here + // Note - this is tweaked to keep optimizers from tossing out the code. + unsigned __int32 tmp = (unsigned __int32)rhs + (unsigned __int32)lhs; + + if( (__int32)tmp >= lhs && SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( (__int32)tmp, result ) ) + return true; + } + + return false; + } + + template < typename E > + static void AdditionThrow( const T& lhs, const U& rhs, T& result ) + { + // rhs is unsigned __int64, lhs signed, 32-bit or less + + if( (unsigned __int32)( rhs >> 32 ) == 0 ) + { + // Now it just happens to work out that the standard behavior does what we want + // Adding explicit casts to show exactly what's happening here + unsigned __int32 tmp = (unsigned __int32)rhs + (unsigned __int32)lhs; + + if( (__int32)tmp >= lhs ) + { + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( (__int32)tmp, result ); + return; + } + } + E::SafeIntOnOverflow(); + } +}; + +enum SubtractionState +{ + SubtractionState_BothUnsigned, + SubtractionState_CastIntCheckMinMax, + SubtractionState_CastIntCheckMin, + SubtractionState_CastInt64CheckMinMax, + SubtractionState_CastInt64CheckMin, + SubtractionState_Uint64Int, + SubtractionState_UintInt64, + SubtractionState_Int64Int, + SubtractionState_IntInt64, + SubtractionState_Int64Uint, + SubtractionState_IntUint64, + SubtractionState_Int64Uint64, + // states for SubtractionMethod2 + SubtractionState_BothUnsigned2, + SubtractionState_CastIntCheckMinMax2, + SubtractionState_CastInt64CheckMinMax2, + SubtractionState_Uint64Int2, + SubtractionState_UintInt642, + SubtractionState_Int64Int2, + SubtractionState_IntInt642, + SubtractionState_Int64Uint2, + SubtractionState_IntUint642, + SubtractionState_Int64Uint642, + SubtractionState_Error +}; + +template < typename T, typename U > class SubtractionMethod +{ +public: + enum + { + // unsigned-unsigned + method = ((IntRegion< T,U >::IntZone_UintLT32_UintLT32 || + (IntRegion< T,U >::IntZone_Uint32_UintLT64) || + (IntRegion< T,U >::IntZone_UintLT32_Uint32) || + (IntRegion< T,U >::IntZone_Uint64_Uint) || + (IntRegion< T,U >::IntZone_UintLT64_Uint64)) ? SubtractionState_BothUnsigned : + // unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? SubtractionState_CastIntCheckMinMax : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? SubtractionState_CastInt64CheckMinMax : + (IntRegion< T,U >::IntZone_Uint64_Int || + IntRegion< T,U >::IntZone_Uint64_Int64) ? SubtractionState_Uint64Int : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? SubtractionState_UintInt64 : + // signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? SubtractionState_CastIntCheckMinMax : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? SubtractionState_CastInt64CheckMinMax : + (IntRegion< T,U >::IntZone_Int64_Int || + IntRegion< T,U >::IntZone_Int64_Int64) ? SubtractionState_Int64Int : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? SubtractionState_IntInt64 : + // signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? SubtractionState_CastIntCheckMin : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? SubtractionState_CastInt64CheckMin : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? SubtractionState_Int64Uint : + (IntRegion< T,U >::IntZone_Int_Uint64) ? SubtractionState_IntUint64 : + (IntRegion< T,U >::IntZone_Int64_Uint64) ? SubtractionState_Int64Uint64 : + SubtractionState_Error) + }; +}; + +// this is for the case of U - SafeInt< T, E > +template < typename T, typename U > class SubtractionMethod2 +{ +public: + enum + { + // unsigned-unsigned + method = ((IntRegion< T,U >::IntZone_UintLT32_UintLT32 || + (IntRegion< T,U >::IntZone_Uint32_UintLT64) || + (IntRegion< T,U >::IntZone_UintLT32_Uint32) || + (IntRegion< T,U >::IntZone_Uint64_Uint) || + (IntRegion< T,U >::IntZone_UintLT64_Uint64)) ? SubtractionState_BothUnsigned2 : + // unsigned-signed + (IntRegion< T,U >::IntZone_UintLT32_IntLT32) ? SubtractionState_CastIntCheckMinMax2 : + (IntRegion< T,U >::IntZone_Uint32_IntLT64 || + IntRegion< T,U >::IntZone_UintLT32_Int32) ? SubtractionState_CastInt64CheckMinMax2 : + (IntRegion< T,U >::IntZone_Uint64_Int || + IntRegion< T,U >::IntZone_Uint64_Int64) ? SubtractionState_Uint64Int2 : + (IntRegion< T,U >::IntZone_UintLT64_Int64) ? SubtractionState_UintInt642 : + // signed-signed + (IntRegion< T,U >::IntZone_IntLT32_IntLT32) ? SubtractionState_CastIntCheckMinMax2 : + (IntRegion< T,U >::IntZone_Int32_IntLT64 || + IntRegion< T,U >::IntZone_IntLT32_Int32) ? SubtractionState_CastInt64CheckMinMax2 : + (IntRegion< T,U >::IntZone_Int64_Int || + IntRegion< T,U >::IntZone_Int64_Int64) ? SubtractionState_Int64Int2 : + (IntRegion< T,U >::IntZone_IntLT64_Int64) ? SubtractionState_IntInt642 : + // signed-unsigned + (IntRegion< T,U >::IntZone_IntLT32_UintLT32) ? SubtractionState_CastIntCheckMinMax2 : + (IntRegion< T,U >::IntZone_Int32_UintLT32 || + IntRegion< T,U >::IntZone_IntLT64_Uint32) ? SubtractionState_CastInt64CheckMinMax2 : + (IntRegion< T,U >::IntZone_Int64_UintLT64) ? SubtractionState_Int64Uint2 : + (IntRegion< T,U >::IntZone_Int_Uint64) ? SubtractionState_IntUint642 : + (IntRegion< T,U >::IntZone_Int64_Uint64) ? SubtractionState_Int64Uint642 : + SubtractionState_Error) + }; +}; + +template < typename T, typename U, int method > class SubtractionHelper; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_BothUnsigned > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // both are unsigned - easy case + if( rhs <= lhs ) + { + result = (T)( lhs - rhs ); + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // both are unsigned - easy case + if( rhs <= lhs ) + { + result = (T)( lhs - rhs ); + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_BothUnsigned2 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, U& result ) throw() + { + // both are unsigned - easy case + // Except we do have to check for overflow - lhs could be larger than result can hold + if( rhs <= lhs ) + { + T tmp = (T)(lhs - rhs); + return SafeCastHelper< U, T, GetCastMethod::method>::Cast( tmp, result); + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, U& result ) + { + // both are unsigned - easy case + if( rhs <= lhs ) + { + T tmp = (T)(lhs - rhs); + SafeCastHelper< U, T, GetCastMethod::method >::template CastThrow( tmp, result); + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastIntCheckMinMax > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + if( SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, result ) ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template class SubtractionHelper< U, T, SubtractionState_CastIntCheckMinMax2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + return SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::Cast( tmp, result ); + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // both values are 16-bit or less + // rhs is signed, so could end up increasing or decreasing + __int32 tmp = lhs - rhs; + + SafeCastHelper< T, __int32, GetCastMethod< T, __int32 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastIntCheckMin > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // both values are 16-bit or less + // rhs is unsigned - check only minimum + __int32 tmp = lhs - rhs; + + if( tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // both values are 16-bit or less + // rhs is unsigned - check only minimum + __int32 tmp = lhs - rhs; + + if( tmp >= (__int32)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastInt64CheckMinMax > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + return SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::Cast( tmp, result ); + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template class SubtractionHelper< U, T, SubtractionState_CastInt64CheckMinMax2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + return SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::Cast( tmp, result ); + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // both values are 32-bit or less + // rhs is signed, so could end up increasing or decreasing + __int64 tmp = (__int64)lhs - (__int64)rhs; + + SafeCastHelper< T, __int64, GetCastMethod< T, __int64 >::method >::template CastThrow< E >( tmp, result ); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_CastInt64CheckMin > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // both values are 32-bit or less + // rhs is unsigned - check only minimum + __int64 tmp = (__int64)lhs - (__int64)rhs; + + if( tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // both values are 32-bit or less + // rhs is unsigned - check only minimum + __int64 tmp = (__int64)lhs - (__int64)rhs; + + if( tmp >= (__int64)IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Uint64Int > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is an unsigned __int64, rhs signed + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (unsigned __int64)rhs ); + return true; + } + } + else + { + T tmp = lhs; + // we're now effectively adding + result = lhs + AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if(result >= tmp) + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is an unsigned __int64, rhs signed + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (unsigned __int64)rhs ); + return; + } + } + else + { + T tmp = lhs; + // we're now effectively adding + result = lhs + AbsValueHelper< U, GetAbsMethod< U >::method >::Abs( rhs ); + + if(result >= tmp) + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Uint64Int2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // U is unsigned __int64, T is signed + if( rhs < 0 ) + { + // treat this as addition + unsigned __int64 tmp; + + tmp = lhs + (unsigned __int64)AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( rhs ); + + // must check for addition overflow and max + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + else if( (unsigned __int64)rhs > lhs ) // now both are positive, so comparison always works + { + // result is negative + // implies that lhs must fit into T, and result cannot overflow + // Also allows us to drop to 32-bit math, which is faster on a 32-bit system + result = (T)lhs - (T)rhs; + return true; + } + else + { + // result is positive + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // U is unsigned __int64, T is signed + if( rhs < 0 ) + { + // treat this as addition + unsigned __int64 tmp; + + tmp = lhs + (unsigned __int64)AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( rhs ); + + // must check for addition overflow and max + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + else if( (unsigned __int64)rhs > lhs ) // now both are positive, so comparison always works + { + // result is negative + // implies that lhs must fit into T, and result cannot overflow + // Also allows us to drop to 32-bit math, which is faster on a 32-bit system + result = (T)lhs - (T)rhs; + return; + } + else + { + // result is positive + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_UintInt64 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is an unsigned int32 or smaller, rhs signed __int64 + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (T)rhs ); + return true; + } + } + else + { + // we're now effectively adding + // since lhs is 32-bit, and rhs cannot exceed 2^63 + // this addition cannot overflow + unsigned __int64 tmp = lhs + ~(unsigned __int64)( rhs ) + 1; // negation safe + + // but we could exceed MaxInt + if(tmp <= IntTraits< T >::maxInt) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is an unsigned int32 or smaller, rhs signed __int64 + // must first see if rhs is positive or negative + if( rhs >= 0 ) + { + if( (unsigned __int64)rhs <= lhs ) + { + result = (T)( lhs - (T)rhs ); + return; + } + } + else + { + // we're now effectively adding + // since lhs is 32-bit, and rhs cannot exceed 2^63 + // this addition cannot overflow + unsigned __int64 tmp = lhs + ~(unsigned __int64)( rhs ) + 1; // negation safe + + // but we could exceed MaxInt + if(tmp <= IntTraits< T >::maxInt) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template class SubtractionHelper< U, T, SubtractionState_UintInt642 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // U unsigned 32-bit or less, T __int64 + if( rhs >= 0 ) + { + // overflow not possible + result = (T)( (__int64)lhs - rhs ); + return true; + } + else + { + // we effectively have an addition + // which cannot overflow internally + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)( -rhs ); + + if( tmp <= (unsigned __int64)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // U unsigned 32-bit or less, T __int64 + if( rhs >= 0 ) + { + // overflow not possible + result = (T)( (__int64)lhs - rhs ); + return; + } + else + { + // we effectively have an addition + // which cannot overflow internally + unsigned __int64 tmp = (unsigned __int64)lhs + (unsigned __int64)( -rhs ); + + if( tmp <= (unsigned __int64)IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Int64Int > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is an __int64, rhs signed (up to 64-bit) + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + // Note - ideally, we can order these so that true conditionals + // lead to success, which enables better pipelining + // It isn't practical here + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || // condition 2 + ( rhs >= 0 && tmp > lhs ) ) // condition 3 + { + return false; + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is an __int64, rhs signed (up to 64-bit) + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + // Note - ideally, we can order these so that true conditionals + // lead to success, which enables better pipelining + // It isn't practical here + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || // condition 2 + ( rhs >= 0 && tmp > lhs ) ) // condition 3 + { + E::SafeIntOnOverflow(); + } + + result = (T)tmp; + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Int64Int2 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // lhs __int64, rhs any signed int (including __int64) + __int64 tmp = lhs - rhs; + + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible in tmp + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible in tmp + + if( lhs >= 0 ) + { + // if both positive, overflow to negative not possible + // which is why we'll explicitly check maxInt, and not call SafeCast + if( ( IntTraits< T >::isLT64Bit && tmp > IntTraits< T >::maxInt ) || + ( rhs < 0 && tmp < lhs ) ) + { + return false; + } + } + else + { + // lhs negative + if( ( IntTraits< T >::isLT64Bit && tmp < IntTraits< T >::minInt) || + ( rhs >=0 && tmp > lhs ) ) + { + return false; + } + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // lhs __int64, rhs any signed int (including __int64) + __int64 tmp = lhs - rhs; + + // we have essentially 4 cases: + // + // 1) lhs positive, rhs positive - overflow not possible in tmp + // 2) lhs positive, rhs negative - equivalent to addition - result >= lhs or error + // 3) lhs negative, rhs positive - check result <= lhs + // 4) lhs negative, rhs negative - overflow not possible in tmp + + if( lhs >= 0 ) + { + // if both positive, overflow to negative not possible + // which is why we'll explicitly check maxInt, and not call SafeCast + if( ( CompileConst< IntTraits< T >::isLT64Bit >::Value() && tmp > IntTraits< T >::maxInt ) || + ( rhs < 0 && tmp < lhs ) ) + { + E::SafeIntOnOverflow(); + } + } + else + { + // lhs negative + if( ( CompileConst< IntTraits< T >::isLT64Bit >::Value() && tmp < IntTraits< T >::minInt) || + ( rhs >=0 && tmp > lhs ) ) + { + E::SafeIntOnOverflow(); + } + } + + result = (T)tmp; + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_IntInt64 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is a 32-bit int or less, rhs __int64 + // we have essentially 4 cases: + // + // lhs positive, rhs positive - rhs could be larger than lhs can represent + // lhs positive, rhs negative - additive case - check tmp >= lhs and tmp > max int + // lhs negative, rhs positive - check tmp <= lhs and tmp < min int + // lhs negative, rhs negative - addition cannot internally overflow, check against max + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // first case + if( rhs >= 0 ) + { + if( tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + } + else + { + // second case + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + } + else + { + // lhs < 0 + // third case + if( rhs >= 0 ) + { + if( tmp <= lhs && tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return true; + } + } + else + { + // fourth case + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return true; + } + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is a 32-bit int or less, rhs __int64 + // we have essentially 4 cases: + // + // lhs positive, rhs positive - rhs could be larger than lhs can represent + // lhs positive, rhs negative - additive case - check tmp >= lhs and tmp > max int + // lhs negative, rhs positive - check tmp <= lhs and tmp < min int + // lhs negative, rhs negative - addition cannot internally overflow, check against max + + __int64 tmp = (__int64)((unsigned __int64)lhs - (unsigned __int64)rhs); + + if( lhs >= 0 ) + { + // first case + if( rhs >= 0 ) + { + if( tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + } + else + { + // second case + if( tmp >= lhs && tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + } + else + { + // lhs < 0 + // third case + if( rhs >= 0 ) + { + if( tmp <= lhs && tmp >= IntTraits< T >::minInt ) + { + result = (T)tmp; + return; + } + } + else + { + // fourth case + if( tmp <= IntTraits< T >::maxInt ) + { + result = (T)tmp; + return; + } + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_IntInt642 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // lhs is any signed int32 or smaller, rhs is int64 + __int64 tmp = (__int64)lhs - rhs; + + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || + ( rhs > 0 && tmp > lhs ) ) + { + return false; + //else OK + } + + result = (T)tmp; + return true; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // lhs is any signed int32 or smaller, rhs is int64 + __int64 tmp = (__int64)lhs - rhs; + + if( ( lhs >= 0 && rhs < 0 && tmp < lhs ) || + ( rhs > 0 && tmp > lhs ) ) + { + E::SafeIntOnOverflow(); + //else OK + } + + result = (T)tmp; + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Int64Uint > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is a 64-bit int, rhs unsigned int32 or smaller + // perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= lhs ) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is a 64-bit int, rhs unsigned int32 or smaller + // perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= lhs ) + { + result = (T)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Int64Uint2 > +{ +public: + // lhs is __int64, rhs is unsigned 32-bit or smaller + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // Do this as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= IntTraits< T >::maxInt && (__int64)tmp >= IntTraits< T >::minInt ) + { + result = (T)(__int64)tmp; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // Do this as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - (unsigned __int64)rhs; + + if( (__int64)tmp <= IntTraits< T >::maxInt && (__int64)tmp >= IntTraits< T >::minInt ) + { + result = (T)(__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_IntUint64 > +{ +public: + static bool Subtract( const T& lhs, const U& rhs, T& result ) throw() + { + // lhs is any signed int, rhs unsigned int64 + // check against available range + + // We need the absolute value of IntTraits< T >::minInt + // This will give it to us without extraneous compiler warnings + const unsigned __int64 AbsMinIntT = (unsigned __int64)IntTraits< T >::maxInt + 1; + + if( lhs < 0 ) + { + if( rhs <= AbsMinIntT - AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( lhs ) ) + { + result = (T)( lhs - rhs ); + return true; + } + } + else + { + if( rhs <= AbsMinIntT + (unsigned __int64)lhs ) + { + result = (T)( lhs - rhs ); + return true; + } + } + + return false; + } + + template < typename E > + static void SubtractThrow( const T& lhs, const U& rhs, T& result ) + { + // lhs is any signed int, rhs unsigned int64 + // check against available range + + // We need the absolute value of IntTraits< T >::minInt + // This will give it to us without extraneous compiler warnings + const unsigned __int64 AbsMinIntT = (unsigned __int64)IntTraits< T >::maxInt + 1; + + if( lhs < 0 ) + { + if( rhs <= AbsMinIntT - AbsValueHelper< T, GetAbsMethod< T >::method >::Abs( lhs ) ) + { + result = (T)( lhs - rhs ); + return; + } + } + else + { + if( rhs <= AbsMinIntT + (unsigned __int64)lhs ) + { + result = (T)( lhs - rhs ); + return; + } + } + + E::SafeIntOnOverflow(); + } +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_IntUint642 > +{ +public: + static bool Subtract( const U& lhs, const T& rhs, T& result ) throw() + { + // We run into upcasting problems on comparison - needs 2 checks + if( lhs >= 0 && (T)lhs >= rhs ) + { + result = (T)((U)lhs - (U)rhs); + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const U& lhs, const T& rhs, T& result ) + { + // We run into upcasting problems on comparison - needs 2 checks + if( lhs >= 0 && (T)lhs >= rhs ) + { + result = (T)((U)lhs - (U)rhs); + return; + } + + E::SafeIntOnOverflow(); + } + +}; + +template < typename T, typename U > class SubtractionHelper< T, U, SubtractionState_Int64Uint64 > +{ +public: + static bool Subtract( const __int64& lhs, const unsigned __int64& rhs, __int64& result ) throw() + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // if we subtract, and it gets larger, there's a problem + // Perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - rhs; + + if( (__int64)tmp <= lhs ) + { + result = (__int64)tmp; + return true; + } + return false; + } + + template < typename E > + static void SubtractThrow( const __int64& lhs, const unsigned __int64& rhs, T& result ) + { + C_ASSERT( IntTraits< T >::isInt64 && IntTraits< U >::isUint64 ); + // if we subtract, and it gets larger, there's a problem + // Perform test as unsigned to prevent unwanted optimizations + unsigned __int64 tmp = (unsigned __int64)lhs - rhs; + + if( (__int64)tmp <= lhs ) + { + result = (__int64)tmp; + return; + } + + E::SafeIntOnOverflow(); + } + +}; + +template < typename U, typename T > class SubtractionHelper< U, T, SubtractionState_Int64Uint642 > +{ +public: + // If lhs is negative, immediate problem - return must be positive, and subtracting only makes it + // get smaller. If rhs > lhs, then it would also go negative, which is the other case + static bool Subtract( const __int64& lhs, const unsigned __int64& rhs, T& result ) throw() + { + C_ASSERT( IntTraits< T >::isUint64 && IntTraits< U >::isInt64 ); + if( lhs >= 0 && (unsigned __int64)lhs >= rhs ) + { + result = (unsigned __int64)lhs - rhs; + return true; + } + + return false; + } + + template < typename E > + static void SubtractThrow( const __int64& lhs, const unsigned __int64& rhs, T& result ) + { + C_ASSERT( IntTraits< T >::isUint64 && IntTraits< U >::isInt64 ); + if( lhs >= 0 && (unsigned __int64)lhs >= rhs ) + { + result = (unsigned __int64)lhs - rhs; + return; + } + + E::SafeIntOnOverflow(); + } + +}; + +enum BinaryState +{ + BinaryState_OK, + BinaryState_Int8, + BinaryState_Int16, + BinaryState_Int32 +}; + +template < typename T, typename U > class BinaryMethod +{ +public: + enum + { + // If both operands are unsigned OR + // return type is smaller than rhs OR + // return type is larger and rhs is unsigned + // Then binary operations won't produce unexpected results + method = ( sizeof( T ) <= sizeof( U ) || + SafeIntCompare< T, U >::isBothUnsigned || + !IntTraits< U >::isSigned ) ? BinaryState_OK : + IntTraits< U >::isInt8 ? BinaryState_Int8 : + IntTraits< U >::isInt16 ? BinaryState_Int16 + : BinaryState_Int32 + }; +}; + +#ifdef SAFEINT_DISABLE_BINARY_ASSERT +#define BinaryAssert(x) +#else +#define BinaryAssert(x) assert(x) +#endif + +template < typename T, typename U, int method > class BinaryAndHelper; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_OK > +{ +public: + static T And( T lhs, U rhs ){ return (T)( lhs & rhs ); } +}; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_Int8 > +{ +public: + static T And( T lhs, U rhs ) + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs & rhs ) == ( lhs & (unsigned __int8)rhs ) ); + return (T)( lhs & (unsigned __int8)rhs ); + } +}; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_Int16 > +{ +public: + static T And( T lhs, U rhs ) + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs & rhs ) == ( lhs & (unsigned __int16)rhs ) ); + return (T)( lhs & (unsigned __int16)rhs ); + } +}; + +template < typename T, typename U > class BinaryAndHelper< T, U, BinaryState_Int32 > +{ +public: + static T And( T lhs, U rhs ) + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs & rhs ) == ( lhs & (unsigned __int32)rhs ) ); + return (T)( lhs & (unsigned __int32)rhs ); + } +}; + +template < typename T, typename U, int method > class BinaryOrHelper; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_OK > +{ +public: + static T Or( T lhs, U rhs ){ return (T)( lhs | rhs ); } +}; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_Int8 > +{ +public: + static T Or( T lhs, U rhs ) + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs | rhs ) == ( lhs | (unsigned __int8)rhs ) ); + return (T)( lhs | (unsigned __int8)rhs ); + } +}; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_Int16 > +{ +public: + static T Or( T lhs, U rhs ) + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs | rhs ) == ( lhs | (unsigned __int16)rhs ) ); + return (T)( lhs | (unsigned __int16)rhs ); + } +}; + +template < typename T, typename U > class BinaryOrHelper< T, U, BinaryState_Int32 > +{ +public: + static T Or( T lhs, U rhs ) + { + //cast forces sign extension to be zeros + BinaryAssert( ( lhs | rhs ) == ( lhs | (unsigned __int32)rhs ) ); + return (T)( lhs | (unsigned __int32)rhs ); + } +}; + +template class BinaryXorHelper; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_OK > +{ +public: + static T Xor( T lhs, U rhs ){ return (T)( lhs ^ rhs ); } +}; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int8 > +{ +public: + static T Xor( T lhs, U rhs ) + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs ^ rhs ) == ( lhs ^ (unsigned __int8)rhs ) ); + return (T)( lhs ^ (unsigned __int8)rhs ); + } +}; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int16 > +{ +public: + static T Xor( T lhs, U rhs ) + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs ^ rhs ) == ( lhs ^ (unsigned __int16)rhs ) ); + return (T)( lhs ^ (unsigned __int16)rhs ); + } +}; + +template < typename T, typename U > class BinaryXorHelper< T, U, BinaryState_Int32 > +{ +public: + static T Xor( T lhs, U rhs ) + { + // cast forces sign extension to be zeros + BinaryAssert( ( lhs ^ rhs ) == ( lhs ^ (unsigned __int32)rhs ) ); + return (T)( lhs ^ (unsigned __int32)rhs ); + } +}; + +/***************** External functions ****************************************/ + +// External functions that can be used where you only need to check one operation +// non-class helper function so that you can check for a cast's validity +// and handle errors how you like +template < typename T, typename U > +inline bool SafeCast( const T From, U& To ) +{ + return SafeCastHelper< U, T, GetCastMethod< U, T >::method >::Cast( From, To ); +} + +template < typename T, typename U > +inline bool SafeEquals( const T t, const U u ) throw() +{ + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( t, u ); +} + +template < typename T, typename U > +inline bool SafeNotEquals( const T t, const U u ) throw() +{ + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( t, u ); +} + +template < typename T, typename U > +inline bool SafeGreaterThan( const T t, const U u ) throw() +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( t, u ); +} + +template < typename T, typename U > +inline bool SafeGreaterThanEquals( const T t, const U u ) throw() +{ + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( u, t ); +} + +template < typename T, typename U > +inline bool SafeLessThan( const T t, const U u ) throw() +{ + return GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( u, t ); +} + +template < typename T, typename U > +inline bool SafeLessThanEquals( const T t, const U u ) throw() +{ + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( t, u ); +} + +template < typename T, typename U > +inline bool SafeModulus( const T& t, const U& u, T& result ) throw() +{ + return ( ModulusHelper< T, U, ValidComparison< T, U >::method >::Modulus( t, u, result ) == SafeIntNoError ); +} + +template < typename T, typename U > +inline bool SafeMultiply( T t, U u, T& result ) throw() +{ + return MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::Multiply( t, u, result ); +} + +template < typename T, typename U > +inline bool SafeDivide( T t, U u, T& result ) throw() +{ + return ( DivisionHelper< T, U, DivisionMethod< T, U >::method >::Divide( t, u, result ) == SafeIntNoError ); +} + +template < typename T, typename U > +inline bool SafeAdd( T t, U u, T& result ) throw() +{ + return AdditionHelper< T, U, AdditionMethod< T, U >::method >::Addition( t, u, result ); +} + +template < typename T, typename U > +inline bool SafeSubtract( T t, U u, T& result ) throw() +{ + return SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::Subtract( t, u, result ); +} + +/***************** end external functions ************************************/ + +// Main SafeInt class +// Assumes exceptions can be thrown +template < typename T, typename E = SafeIntDefaultExceptionHandler > class SafeInt +{ +public: + SafeInt() throw() + { + C_ASSERT( NumericType< T >::isInt ); + m_int = 0; + } + + // Having a constructor for every type of int + // avoids having the compiler evade our checks when doing implicit casts - + // e.g., SafeInt s = 0x7fffffff; + SafeInt( const T& i ) throw() + { + C_ASSERT( NumericType< T >::isInt ); + //always safe + m_int = i; + } + + // provide explicit boolean converter + SafeInt( bool b ) throw() + { + C_ASSERT( NumericType< T >::isInt ); + m_int = (T)( b ? 1 : 0 ); + } + + template < typename U > + SafeInt(const SafeInt< U, E >& u) + { + C_ASSERT( NumericType< T >::isInt ); + *this = SafeInt< T, E >( (U)u ); + } + + template < typename U > + SafeInt( const U& i ) + { + C_ASSERT( NumericType< T >::isInt ); + // SafeCast will throw exceptions if i won't fit in type T + SafeCastHelper< T, U, GetCastMethod< T, U >::method >::template CastThrow< E >( i, m_int ); + } + + // The destructor is intentionally commented out - no destructor + // vs. a do-nothing destructor makes a huge difference in + // inlining characteristics. It wasn't doing anything anyway. + // ~SafeInt(){}; + + + // now start overloading operators + // assignment operator + // constructors exist for all int types and will ensure safety + + template < typename U > + SafeInt< T, E >& operator =( const U& rhs ) + { + // use constructor to test size + // constructor is optimized to do minimal checking based + // on whether T can contain U + // note - do not change this + *this = SafeInt< T, E >( rhs ); + return *this; + } + + SafeInt< T, E >& operator =( const T& rhs ) throw() + { + m_int = rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator =( const SafeInt< U, E >& rhs ) + { + SafeCastHelper< T, U, GetCastMethod< T, U >::method >::template CastThrow< E >( rhs.Ref(), m_int ); + return *this; + } + + SafeInt< T, E >& operator =( const SafeInt< T, E >& rhs ) throw() + { + m_int = rhs.m_int; + return *this; + } + + // Casting operators + + operator bool() const throw() + { + return !!m_int; + } + + operator char() const + { + char val; + SafeCastHelper< char, T, GetCastMethod< char, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator signed char() const + { + signed char val; + SafeCastHelper< signed char, T, GetCastMethod< signed char, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned char() const + { + unsigned char val; + SafeCastHelper< unsigned char, T, GetCastMethod< unsigned char, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator __int16() const + { + __int16 val; + SafeCastHelper< __int16, T, GetCastMethod< __int16, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned __int16() const + { + unsigned __int16 val; + SafeCastHelper< unsigned __int16, T, GetCastMethod< unsigned __int16, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator __int32() const + { + __int32 val; + SafeCastHelper< __int32, T, GetCastMethod< __int32, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned __int32() const + { + unsigned __int32 val; + SafeCastHelper< unsigned __int32, T, GetCastMethod< unsigned __int32, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + // The compiler knows that int == __int32 + // but not that long == __int32 + operator long() const + { + long val; + SafeCastHelper< long, T, GetCastMethod< long, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned long() const + { + unsigned long val; + SafeCastHelper< unsigned long, T, GetCastMethod< unsigned long, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator __int64() const + { + __int64 val; + SafeCastHelper< __int64, T, GetCastMethod< __int64, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator unsigned __int64() const + { + unsigned __int64 val; + SafeCastHelper< unsigned __int64, T, GetCastMethod< unsigned __int64, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + +#ifdef SIZE_T_CAST_NEEDED + // We also need an explicit cast to size_t, or the compiler will complain + // Apparently, only SOME compilers complain, and cl 14.00.50727.42 isn't one of them + // Leave here in case we decide to backport this to an earlier compiler + operator size_t() const + { + size_t val; + SafeCastHelper< size_t, T, GetCastMethod< size_t, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } +#endif + + // Also provide a cast operator for floating point types + operator float() const + { + float val; + SafeCastHelper< float, T, GetCastMethod< float, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + operator double() const + { + double val; + SafeCastHelper< double, T, GetCastMethod< double, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + operator long double() const + { + long double val; + SafeCastHelper< long double, T, GetCastMethod< long double, T >::method >::template CastThrow< E >( m_int, val ); + return val; + } + + // If you need a pointer to the data + // this could be dangerous, but allows you to correctly pass + // instances of this class to APIs that take a pointer to an integer + // also see overloaded address-of operator below + T* Ptr() throw() { return &m_int; } + const T* Ptr() const throw() { return &m_int; } + const T& Ref() const throw() { return m_int; } + + // Or if SafeInt< T, E >::Ptr() is inconvenient, use the overload + // operator & + // This allows you to do unsafe things! + // It is meant to allow you to more easily + // pass a SafeInt into things like ReadFile + T* operator &() throw() { return &m_int; } + const T* operator &() const throw() { return &m_int; } + + // Unary operators + bool operator !() const throw() { return (!m_int) ? true : false; } + + // operator + (unary) + // note - normally, the '+' and '-' operators will upcast to a signed int + // for T < 32 bits. This class changes behavior to preserve type + const SafeInt< T, E >& operator +() const throw() { return *this; }; + + //unary - + + SafeInt< T, E > operator -() const + { + // Note - unsigned still performs the bitwise manipulation + // will warn at level 2 or higher if the value is 32-bit or larger + return SafeInt(NegationHelper::isSigned>::template NegativeThrow(m_int)); + } + + // prefix increment operator + SafeInt< T, E >& operator ++() + { + if( m_int != IntTraits< T >::maxInt ) + { + ++m_int; + return *this; + } + E::SafeIntOnOverflow(); + } + + // prefix decrement operator + SafeInt< T, E >& operator --() + { + if( m_int != IntTraits< T >::minInt ) + { + --m_int; + return *this; + } + E::SafeIntOnOverflow(); + } + + // note that postfix operators have inherently worse perf + // characteristics + + // postfix increment operator + SafeInt< T, E > operator ++( int ) // dummy arg to comply with spec + { + if( m_int != IntTraits< T >::maxInt ) + { + SafeInt< T, E > tmp( m_int ); + + m_int++; + return tmp; + } + E::SafeIntOnOverflow(); + } + + // postfix decrement operator + SafeInt< T, E > operator --( int ) // dummy arg to comply with spec + { + if( m_int != IntTraits< T >::minInt ) + { + SafeInt< T, E > tmp( m_int ); + m_int--; + return tmp; + } + E::SafeIntOnOverflow(); + } + + // One's complement + // Note - this operator will normally change size to an int + // cast in return improves perf and maintains type + SafeInt< T, E > operator ~() const throw() { return SafeInt< T, E >( (T)~m_int ); } + + // Binary operators + // + // arithmetic binary operators + // % modulus + // * multiplication + // / division + // + addition + // - subtraction + // + // For each of the arithmetic operators, you will need to + // use them as follows: + // + // SafeInt c = 2; + // SafeInt i = 3; + // + // SafeInt i2 = i op (char)c; + // OR + // SafeInt i2 = (int)i op c; + // + // The base problem is that if the lhs and rhs inputs are different SafeInt types + // it is not possible in this implementation to determine what type of SafeInt + // should be returned. You have to let the class know which of the two inputs + // need to be the return type by forcing the other value to the base integer type. + // + // Note - as per feedback from Scott Meyers, I'm exploring how to get around this. + // 3.0 update - I'm still thinking about this. It can be done with template metaprogramming, + // but it is tricky, and there's a perf vs. correctness tradeoff where the right answer + // is situational. + // + // The case of: + // + // SafeInt< T, E > i, j, k; + // i = j op k; + // + // works just fine and no unboxing is needed because the return type is not ambiguous. + + // Modulus + // Modulus has some convenient properties - + // first, the magnitude of the return can never be + // larger than the lhs operand, and it must be the same sign + // as well. It does, however, suffer from the same promotion + // problems as comparisons, division and other operations + template < typename U > + SafeInt< T, E > operator %( U rhs ) const + { + T result; + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( m_int, rhs, result ); + return SafeInt< T, E >( result ); + } + + SafeInt< T, E > operator %( SafeInt< T, E > rhs ) const + { + T result; + ModulusHelper< T, T, ValidComparison< T, T >::method >::template ModulusThrow< E >( m_int, rhs, result ); + return SafeInt< T, E >( result ); + } + + // Modulus assignment + template < typename U > + SafeInt< T, E >& operator %=( U rhs ) + { + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator %=( SafeInt< U, E > rhs ) + { + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( m_int, (U)rhs, m_int ); + return *this; + } + + // Multiplication + template < typename U > + SafeInt< T, E > operator *( U rhs ) const + { + T ret( 0 ); + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + SafeInt< T, E > operator *( SafeInt< T, E > rhs ) const + { + T ret( 0 ); + MultiplicationHelper< T, T, MultiplicationMethod< T, T >::method >::template MultiplyThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + // Multiplication assignment + SafeInt< T, E >& operator *=( SafeInt< T, E > rhs ) + { + MultiplicationHelper< T, T, MultiplicationMethod< T, T >::method >::template MultiplyThrow< E >( m_int, (T)rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator *=( U rhs ) + { + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator *=( SafeInt< U, E > rhs ) + { + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( m_int, rhs.Ref(), m_int ); + return *this; + } + + // Division + template < typename U > + SafeInt< T, E > operator /( U rhs ) const + { + T ret( 0 ); + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + SafeInt< T, E > operator /( SafeInt< T, E > rhs ) const + { + T ret( 0 ); + DivisionHelper< T, T, DivisionMethod< T, T >::method >::template DivideThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + // Division assignment + SafeInt< T, E >& operator /=( SafeInt< T, E > i ) + { + DivisionHelper< T, T, DivisionMethod< T, T >::method >::template DivideThrow< E >( m_int, (T)i, m_int ); + return *this; + } + + template < typename U > SafeInt< T, E >& operator /=( U i ) + { + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( m_int, i, m_int ); + return *this; + } + + template < typename U > SafeInt< T, E >& operator /=( SafeInt< U, E > i ) + { + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( m_int, (U)i, m_int ); + return *this; + } + + // For addition and subtraction + + // Addition + SafeInt< T, E > operator +( SafeInt< T, E > rhs ) const + { + T ret( 0 ); + AdditionHelper< T, T, AdditionMethod< T, T >::method >::template AdditionThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + template < typename U > + SafeInt< T, E > operator +( U rhs ) const + { + T ret( 0 ); + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + //addition assignment + SafeInt< T, E >& operator +=( SafeInt< T, E > rhs ) + { + AdditionHelper< T, T, AdditionMethod< T, T >::method >::template AdditionThrow< E >( m_int, (T)rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator +=( U rhs ) + { + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator +=( SafeInt< U, E > rhs ) + { + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( m_int, (U)rhs, m_int ); + return *this; + } + + // Subtraction + template < typename U > + SafeInt< T, E > operator -( U rhs ) const + { + T ret( 0 ); + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( m_int, rhs, ret ); + return SafeInt< T, E >( ret ); + } + + SafeInt< T, E > operator -(SafeInt< T, E > rhs) const + { + T ret( 0 ); + SubtractionHelper< T, T, SubtractionMethod< T, T >::method >::template SubtractThrow< E >( m_int, (T)rhs, ret ); + return SafeInt< T, E >( ret ); + } + + // Subtraction assignment + SafeInt< T, E >& operator -=( SafeInt< T, E > rhs ) + { + SubtractionHelper< T, T, SubtractionMethod< T, T >::method >::template SubtractThrow< E >( m_int, (T)rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator -=( U rhs ) + { + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( m_int, rhs, m_int ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator -=( SafeInt< U, E > rhs ) + { + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( m_int, (U)rhs, m_int ); + return *this; + } + + // Comparison operators + // Additional overloads defined outside the class + // to allow for cases where the SafeInt is the rhs value + + // Less than + template < typename U > + bool operator <( U rhs ) const throw() + { + return GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( rhs, m_int ); + } + + bool operator <( SafeInt< T, E > rhs ) const throw() + { + return m_int < (T)rhs; + } + + // Greater than or eq. + template < typename U > + bool operator >=( U rhs ) const throw() + { + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( rhs, m_int ); + } + + bool operator >=( SafeInt< T, E > rhs ) const throw() + { + return m_int >= (T)rhs; + } + + // Greater than + template < typename U > + bool operator >( U rhs ) const throw() + { + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( m_int, rhs ); + } + + bool operator >( SafeInt< T, E > rhs ) const throw() + { + return m_int > (T)rhs; + } + + // Less than or eq. + template < typename U > + bool operator <=( U rhs ) const throw() + { + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( m_int, rhs ); + } + + bool operator <=( SafeInt< T, E > rhs ) const throw() + { + return m_int <= (T)rhs; + } + + // Equality + template < typename U > + bool operator ==( U rhs ) const throw() + { + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( m_int, rhs ); + } + + // Need an explicit override for type bool + bool operator ==( bool rhs ) const throw() + { + return ( m_int == 0 ? false : true ) == rhs; + } + + bool operator ==( SafeInt< T, E > rhs ) const throw() { return m_int == (T)rhs; } + + // != operators + template < typename U > + bool operator !=( U rhs ) const throw() + { + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( m_int, rhs ); + } + + bool operator !=( bool b ) const throw() + { + return ( m_int == 0 ? false : true ) != b; + } + + bool operator !=( SafeInt< T, E > rhs ) const throw() { return m_int != (T)rhs; } + + // Shift operators + // Note - shift operators ALWAYS return the same type as the lhs + // specific version for SafeInt< T, E > not needed - + // code path is exactly the same as for SafeInt< U, E > as rhs + + // Left shift + // Also, shifting > bitcount is undefined - trap in debug +#ifdef SAFEINT_DISABLE_SHIFT_ASSERT +#define ShiftAssert(x) +#else +#define ShiftAssert(x) assert(x) +#endif + + template < typename U > + SafeInt< T, E > operator <<( U bits ) const throw() + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)( m_int << bits ) ); + } + + template < typename U > + SafeInt< T, E > operator <<( SafeInt< U, E > bits ) const throw() + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( (U)bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)( m_int << (U)bits ) ); + } + + // Left shift assignment + + template < typename U > + SafeInt< T, E >& operator <<=( U bits ) throw() + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + m_int <<= bits; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator <<=( SafeInt< U, E > bits ) throw() + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( (U)bits < (int)IntTraits< T >::bitCount ); + + m_int <<= (U)bits; + return *this; + } + + // Right shift + template < typename U > + SafeInt< T, E > operator >>( U bits ) const throw() + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)( m_int >> bits ) ); + } + + template < typename U > + SafeInt< T, E > operator >>( SafeInt< U, E > bits ) const throw() + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + return SafeInt< T, E >( (T)(m_int >> (U)bits) ); + } + + // Right shift assignment + template < typename U > + SafeInt< T, E >& operator >>=( U bits ) throw() + { + ShiftAssert( !IntTraits< U >::isSigned || bits >= 0 ); + ShiftAssert( bits < (int)IntTraits< T >::bitCount ); + + m_int >>= bits; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator >>=( SafeInt< U, E > bits ) throw() + { + ShiftAssert( !IntTraits< U >::isSigned || (U)bits >= 0 ); + ShiftAssert( (U)bits < (int)IntTraits< T >::bitCount ); + + m_int >>= (U)bits; + return *this; + } + + // Bitwise operators + // This only makes sense if we're dealing with the same type and size + // demand a type T, or something that fits into a type T + + // Bitwise & + SafeInt< T, E > operator &( SafeInt< T, E > rhs ) const throw() + { + return SafeInt< T, E >( m_int & (T)rhs ); + } + + template < typename U > + SafeInt< T, E > operator &( U rhs ) const throw() + { + // we want to avoid setting bits by surprise + // consider the case of lhs = int, value = 0xffffffff + // rhs = char, value = 0xff + // + // programmer intent is to get only the lower 8 bits + // normal behavior is to upcast both sides to an int + // which then sign extends rhs, setting all the bits + + // If you land in the assert, this is because the bitwise operator + // was causing unexpected behavior. Fix is to properly cast your inputs + // so that it works like you meant, not unexpectedly + + return SafeInt< T, E >( BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( m_int, rhs ) ); + } + + // Bitwise & assignment + SafeInt< T, E >& operator &=( SafeInt< T, E > rhs ) throw() + { + m_int &= (T)rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator &=( U rhs ) throw() + { + m_int = BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( m_int, rhs ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator &=( SafeInt< U, E > rhs ) throw() + { + m_int = BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( m_int, (U)rhs ); + return *this; + } + + // XOR + SafeInt< T, E > operator ^( SafeInt< T, E > rhs ) const throw() + { + return SafeInt< T, E >( (T)( m_int ^ (T)rhs ) ); + } + + template < typename U > + SafeInt< T, E > operator ^( U rhs ) const throw() + { + // If you land in the assert, this is because the bitwise operator + // was causing unexpected behavior. Fix is to properly cast your inputs + // so that it works like you meant, not unexpectedly + + return SafeInt< T, E >( BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( m_int, rhs ) ); + } + + // XOR assignment + SafeInt< T, E >& operator ^=( SafeInt< T, E > rhs ) throw() + { + m_int ^= (T)rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator ^=( U rhs ) throw() + { + m_int = BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( m_int, rhs ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator ^=( SafeInt< U, E > rhs ) throw() + { + m_int = BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( m_int, (U)rhs ); + return *this; + } + + // bitwise OR + SafeInt< T, E > operator |( SafeInt< T, E > rhs ) const throw() + { + return SafeInt< T, E >( (T)( m_int | (T)rhs ) ); + } + + template < typename U > + SafeInt< T, E > operator |( U rhs ) const throw() + { + return SafeInt< T, E >( BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( m_int, rhs ) ); + } + + // bitwise OR assignment + SafeInt< T, E >& operator |=( SafeInt< T, E > rhs ) throw() + { + m_int |= (T)rhs; + return *this; + } + + template < typename U > + SafeInt< T, E >& operator |=( U rhs ) throw() + { + m_int = BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( m_int, rhs ); + return *this; + } + + template < typename U > + SafeInt< T, E >& operator |=( SafeInt< U, E > rhs ) throw() + { + m_int = BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( m_int, (U)rhs ); + return *this; + } + + // Miscellaneous helper functions + SafeInt< T, E > Min( SafeInt< T, E > test, const T floor = IntTraits< T >::minInt ) const throw() + { + T tmp = test < m_int ? (T)test : m_int; + return tmp < floor ? floor : tmp; + } + + SafeInt< T, E > Max( SafeInt< T, E > test, const T upper = IntTraits< T >::maxInt ) const throw() + { + T tmp = test > m_int ? (T)test : m_int; + return tmp > upper ? upper : tmp; + } + + void Swap( SafeInt< T, E >& with ) throw() + { + T temp( m_int ); + m_int = with.m_int; + with.m_int = temp; + } + + static SafeInt< T, E > SafeAtoI( const char* input ) + { + return SafeTtoI( input ); + } + + static SafeInt< T, E > SafeWtoI( const wchar_t* input ) + { + return SafeTtoI( input ); + } + + enum alignBits + { + align2 = 1, + align4 = 2, + align8 = 3, + align16 = 4, + align32 = 5, + align64 = 6, + align128 = 7, + align256 = 8 + }; + + template < alignBits bits > + const SafeInt< T, E >& Align() + { + // Zero is always aligned + if( m_int == 0 ) + return *this; + + // We don't support aligning negative numbers at this time + // Can't align unsigned numbers on bitCount (e.g., 8 bits = 256, unsigned char max = 255) + // or signed numbers on bitCount-1 (e.g., 7 bits = 128, signed char max = 127). + // Also makes no sense to try to align on negative or no bits. + + ShiftAssert( ( ( IntTraits::isSigned && bits < (int)IntTraits< T >::bitCount - 1 ) + || ( !IntTraits::isSigned && bits < (int)IntTraits< T >::bitCount ) ) && + bits >= 0 && ( !IntTraits::isSigned || m_int > 0 ) ); + + const T AlignValue = ( (T)1 << bits ) - 1; + + m_int = (T)( ( m_int + AlignValue ) & ~AlignValue ); + + if( m_int <= 0 ) + E::SafeIntOnOverflow(); + + return *this; + } + + // Commonly needed alignments: + const SafeInt< T, E >& Align2() { return Align< align2 >(); } + const SafeInt< T, E >& Align4() { return Align< align4 >(); } + const SafeInt< T, E >& Align8() { return Align< align8 >(); } + const SafeInt< T, E >& Align16() { return Align< align16 >(); } + const SafeInt< T, E >& Align32() { return Align< align32 >(); } + const SafeInt< T, E >& Align64() { return Align< align64 >(); } +private: + + // This is almost certainly not the best optimized version of atoi, + // but it does not display a typical bug where it isn't possible to set MinInt + // and it won't allow you to overflow your integer. + // This is here because it is useful, and it is an example of what + // can be done easily with SafeInt. + template < typename U > + static SafeInt< T, E > SafeTtoI( U* input ) + { + U* tmp = input; + SafeInt< T, E > s; + bool negative = false; + + // Bad input, or empty string + if( input == nullptr || input[0] == 0 ) + E::SafeIntOnOverflow(); + + switch( *tmp ) + { + case '-': + tmp++; + negative = true; + break; + case '+': + tmp++; + break; + } + + while( *tmp != 0 ) + { + if( *tmp < '0' || *tmp > '9' ) + break; + + if( (T)s != 0 ) + s *= (T)10; + + if( !negative ) + s += (T)( *tmp - '0' ); + else + s -= (T)( *tmp - '0' ); + + tmp++; + } + + return s; + } + + T m_int; +}; + +// Helper function used to subtract pointers. +// Used to squelch warnings +template +SafeInt SafePtrDiff(const P* p1, const P* p2) +{ + return SafeInt( p1 - p2 ); +} + +// Externally defined functions for the case of U op SafeInt< T, E > +template < typename T, typename U, typename E > +bool operator <( U lhs, SafeInt< T, E > rhs ) throw() +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)rhs, lhs ); +} + +template < typename T, typename U, typename E > +bool operator <( SafeInt< U, E > lhs, SafeInt< T, E > rhs ) throw() +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)rhs, (U)lhs ); +} + +// Greater than +template < typename T, typename U, typename E > +bool operator >( U lhs, SafeInt< T, E > rhs ) throw() +{ + return GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( lhs, (T)rhs ); +} + +template < typename T, typename U, typename E > +bool operator >( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) throw() +{ + return GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)lhs, (U)rhs ); +} + +// Greater than or equal +template < typename T, typename U, typename E > +bool operator >=( U lhs, SafeInt< T, E > rhs ) throw() +{ + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)rhs, lhs ); +} + +template < typename T, typename U, typename E > +bool operator >=( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) throw() +{ + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( (U)rhs, (T)lhs ); +} + +// Less than or equal +template < typename T, typename U, typename E > +bool operator <=( U lhs, SafeInt< T, E > rhs ) throw() +{ + return !GreaterThanTest< U, T, ValidComparison< U, T >::method >::GreaterThan( lhs, (T)rhs ); +} + +template < typename T, typename U, typename E > +bool operator <=( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) throw() +{ + return !GreaterThanTest< T, U, ValidComparison< T, U >::method >::GreaterThan( (T)lhs, (U)rhs ); +} + +// equality +// explicit overload for bool +template < typename T, typename E > +bool operator ==( bool lhs, SafeInt< T, E > rhs ) throw() +{ + return lhs == ( (T)rhs == 0 ? false : true ); +} + +template < typename T, typename U, typename E > +bool operator ==( U lhs, SafeInt< T, E > rhs ) throw() +{ + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals((T)rhs, lhs); +} + +template < typename T, typename U, typename E > +bool operator ==( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) throw() +{ + return EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( (T)lhs, (U)rhs ); +} + +//not equals +template < typename T, typename U, typename E > +bool operator !=( U lhs, SafeInt< T, E > rhs ) throw() +{ + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( rhs, lhs ); +} + +template < typename T, typename E > +bool operator !=( bool lhs, SafeInt< T, E > rhs ) throw() +{ + return ( (T)rhs == 0 ? false : true ) != lhs; +} + +template < typename T, typename U, typename E > +bool operator !=( SafeInt< T, E > lhs, SafeInt< U, E > rhs ) throw() +{ + return !EqualityTest< T, U, ValidComparison< T, U >::method >::IsEquals( lhs, rhs ); +} + +template < typename T, typename U, typename E, int method > class ModulusSimpleCaseHelper; + +template < typename T, typename E, int method > class ModulusSignedCaseHelper; + +template < typename T, typename E > class ModulusSignedCaseHelper < T, E, true > +{ +public: + static bool SignedCase( SafeInt< T, E > rhs, SafeInt< T, E >& result ) + { + if( (T)rhs == (T)-1 ) + { + result = 0; + return true; + } + return false; + } +}; + +template < typename T, typename E > class ModulusSignedCaseHelper < T, E, false > +{ +public: + static bool SignedCase( SafeInt< T, E > /*rhs*/, SafeInt< T, E >& /*result*/ ) + { + return false; + } +}; + +template < typename T, typename U, typename E > +class ModulusSimpleCaseHelper < T, U, E, true > +{ +public: + static bool ModulusSimpleCase( U lhs, SafeInt< T, E > rhs, SafeInt< T, E >& result ) + { + if( rhs != 0 ) + { + if( ModulusSignedCaseHelper< T, E, IntTraits< T >::isSigned >::SignedCase( rhs, result ) ) + return true; + + result = SafeInt< T, E >( (T)( lhs % (T)rhs ) ); + return true; + } + + E::SafeIntOnDivZero(); + NotReachedReturn(false); + } +}; + +template< typename T, typename U, typename E > +class ModulusSimpleCaseHelper < T, U, E, false > +{ +public: + static bool ModulusSimpleCase( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt< T, E >& /*result*/ ) + { + return false; + } +}; + +// Modulus +template < typename T, typename U, typename E > +SafeInt< T, E > operator %( U lhs, SafeInt< T, E > rhs ) +{ + // Value of return depends on sign of lhs + // This one may not be safe - bounds check in constructor + // if lhs is negative and rhs is unsigned, this will throw an exception. + + // Fast-track the simple case + // same size and same sign + SafeInt< T, E > result; + + if( ModulusSimpleCaseHelper< T, U, E, + sizeof(T) == sizeof(U) && (bool)IntTraits< T >::isSigned == (bool)IntTraits< U >::isSigned >::ModulusSimpleCase( lhs, rhs, result ) ) + return result; + + return SafeInt< T, E >( ( SafeInt< U, E >( lhs ) % (T)rhs ) ); +} + +// Multiplication +template < typename T, typename U, typename E > +SafeInt< T, E > operator *( U lhs, SafeInt< T, E > rhs ) +{ + T ret( 0 ); + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( (T)rhs, lhs, ret ); + return SafeInt< T, E >(ret); +} + +template < typename T, typename U, typename E, int method > class DivisionNegativeCornerCaseHelper; + +template < typename T, typename U, typename E > class DivisionNegativeCornerCaseHelper< T, U, E, true > +{ +public: + static bool NegativeCornerCase( U lhs, SafeInt< T, E > rhs, SafeInt& result ) + { + // Problem case - normal casting behavior changes meaning + // flip rhs to positive + // any operator casts now do the right thing + U tmp; + + if( CompileConst< sizeof(T) == 4 >::Value() ) + tmp = lhs/(U)( ~(unsigned __int32)(T)rhs + 1 ); + else + tmp = lhs/(U)( ~(unsigned __int64)(T)rhs + 1 ); + + if( tmp <= (U)IntTraits< T >::maxInt ) + { + result = SafeInt< T, E >( (T)(~(unsigned __int64)tmp + 1) ); + return true; + } + + // Corner case + T maxT = IntTraits< T >::maxInt; + if( tmp == (U)maxT + 1 ) + { + T minT = IntTraits< T >::minInt; + result = SafeInt< T, E >( minT ); + return true; + } + + E::SafeIntOnOverflow(); + NotReachedReturn(false); + } +}; + +template < typename T, typename U, typename E > class DivisionNegativeCornerCaseHelper< T, U, E, false > +{ +public: + static bool NegativeCornerCase( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt& /*result*/ ) + { + return false; + } +}; + +template < typename T, typename U, typename E, int method > class DivisionCornerCaseHelper; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper < T, U, E, true > +{ +public: + static bool DivisionCornerCase1( U lhs, SafeInt< T, E > rhs, SafeInt& result ) + { + if( (T)rhs > 0 ) + { + result = SafeInt< T, E >( lhs/(T)rhs ); + return true; + } + + // Now rhs is either negative, or zero + if( (T)rhs != 0 ) + { + if( DivisionNegativeCornerCaseHelper< T, U, E, sizeof( U ) >= 4 && sizeof( T ) <= sizeof( U ) >::NegativeCornerCase( lhs, rhs, result ) ) + return true; + + result = SafeInt< T, E >(lhs/(T)rhs); + return true; + } + + E::SafeIntOnDivZero(); + NotReachedReturn(false); + } +}; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper < T, U, E, false > +{ +public: + static bool DivisionCornerCase1( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt& /*result*/ ) + { + return false; + } +}; + +template < typename T, typename U, typename E, int method > class DivisionCornerCaseHelper2; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper2 < T, U, E, true > +{ +public: + static bool DivisionCornerCase2( U lhs, SafeInt< T, E > rhs, SafeInt& result ) + { + if( lhs == IntTraits< U >::minInt && (T)rhs == -1 ) + { + // corner case of a corner case - lhs = min int, rhs = -1, + // but rhs is the return type, so in essence, we can return -lhs + // if rhs is a larger type than lhs + // If types are wrong, throws + +#if !defined __GNUC__ +#pragma warning(push) + //cast truncates constant value +#pragma warning(disable:4310) +#endif + + if( CompileConst::Value() ) + result = SafeInt< T, E >( (T)( -(T)IntTraits< U >::minInt ) ); + else + E::SafeIntOnOverflow(); + +#if !defined __GNUC__ +#pragma warning(pop) +#endif + + return true; + } + + return false; + } +}; + +template < typename T, typename U, typename E > class DivisionCornerCaseHelper2 < T, U, E, false > +{ +public: + static bool DivisionCornerCase2( U /*lhs*/, SafeInt< T, E > /*rhs*/, SafeInt& /*result*/ ) + { + return false; + } +}; + +// Division +template < typename T, typename U, typename E > SafeInt< T, E > operator /( U lhs, SafeInt< T, E > rhs ) +{ + // Corner case - has to be handled seperately + SafeInt< T, E > result; + if( DivisionCornerCaseHelper< T, U, E, (int)DivisionMethod< U, T >::method == (int)DivisionState_UnsignedSigned >::DivisionCornerCase1( lhs, rhs, result ) ) + return result; + + if( DivisionCornerCaseHelper2< T, U, E, SafeIntCompare< T, U >::isBothSigned >::DivisionCornerCase2( lhs, rhs, result ) ) + return result; + + // Otherwise normal logic works with addition of bounds check when casting from U->T + U ret; + DivisionHelper< U, T, DivisionMethod< U, T >::method >::template DivideThrow< E >( lhs, (T)rhs, ret ); + return SafeInt< T, E >( ret ); +} + +// Addition +template < typename T, typename U, typename E > +SafeInt< T, E > operator +( U lhs, SafeInt< T, E > rhs ) +{ + T ret( 0 ); + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( (T)rhs, lhs, ret ); + return SafeInt< T, E >( ret ); +} + +// Subtraction +template < typename T, typename U, typename E > +SafeInt< T, E > operator -( U lhs, SafeInt< T, E > rhs ) +{ + T ret( 0 ); + SubtractionHelper< U, T, SubtractionMethod2< U, T >::method >::template SubtractThrow< E >( lhs, rhs.Ref(), ret ); + + return SafeInt< T, E >( ret ); +} + +// Overrides designed to deal with cases where a SafeInt is assigned out +// to a normal int - this at least makes the last operation safe +// += +template < typename T, typename U, typename E > +T& operator +=( T& lhs, SafeInt< U, E > rhs ) +{ + T ret( 0 ); + AdditionHelper< T, U, AdditionMethod< T, U >::method >::template AdditionThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator -=( T& lhs, SafeInt< U, E > rhs ) +{ + T ret( 0 ); + SubtractionHelper< T, U, SubtractionMethod< T, U >::method >::template SubtractThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator *=( T& lhs, SafeInt< U, E > rhs ) +{ + T ret( 0 ); + MultiplicationHelper< T, U, MultiplicationMethod< T, U >::method >::template MultiplyThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator /=( T& lhs, SafeInt< U, E > rhs ) +{ + T ret( 0 ); + DivisionHelper< T, U, DivisionMethod< T, U >::method >::template DivideThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator %=( T& lhs, SafeInt< U, E > rhs ) +{ + T ret( 0 ); + ModulusHelper< T, U, ValidComparison< T, U >::method >::template ModulusThrow< E >( lhs, (U)rhs, ret ); + lhs = ret; + return lhs; +} + +template < typename T, typename U, typename E > +T& operator &=( T& lhs, SafeInt< U, E > rhs ) throw() +{ + lhs = BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( lhs, (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator ^=( T& lhs, SafeInt< U, E > rhs ) throw() +{ + lhs = BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( lhs, (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator |=( T& lhs, SafeInt< U, E > rhs ) throw() +{ + lhs = BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( lhs, (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator <<=( T& lhs, SafeInt< U, E > rhs ) throw() +{ + lhs = (T)( SafeInt< T, E >( lhs ) << (U)rhs ); + return lhs; +} + +template < typename T, typename U, typename E > +T& operator >>=( T& lhs, SafeInt< U, E > rhs ) throw() +{ + lhs = (T)( SafeInt< T, E >( lhs ) >> (U)rhs ); + return lhs; +} + +// Specific pointer overrides +// Note - this function makes no attempt to ensure +// that the resulting pointer is still in the buffer, only +// that no int overflows happened on the way to getting the new pointer +template < typename T, typename U, typename E > +T*& operator +=( T*& lhs, SafeInt< U, E > rhs ) +{ + // Cast the pointer to a number so we can do arithmetic + SafeInt< size_t, E > ptr_val = reinterpret_cast< size_t >( lhs ); + // Check first that rhs is valid for the type of ptrdiff_t + // and that multiplying by sizeof( T ) doesn't overflow a ptrdiff_t + // Next, we need to add 2 SafeInts of different types, so unbox the ptr_diff + // Finally, cast the number back to a pointer of the correct type + lhs = reinterpret_cast< T* >( (size_t)( ptr_val + (ptrdiff_t)( SafeInt< ptrdiff_t, E >( rhs ) * sizeof( T ) ) ) ); + return lhs; +} + +template < typename T, typename U, typename E > +T*& operator -=( T*& lhs, SafeInt< U, E > rhs ) +{ + // Cast the pointer to a number so we can do arithmetic + SafeInt< size_t, E > ptr_val = reinterpret_cast< size_t >( lhs ); + // See above for comments + lhs = reinterpret_cast< T* >( (size_t)( ptr_val - (ptrdiff_t)( SafeInt< ptrdiff_t, E >( rhs ) * sizeof( T ) ) ) ); + return lhs; +} + +template < typename T, typename U, typename E > +T*& operator *=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator /=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator %=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator &=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator ^=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator |=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator <<=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +template < typename T, typename U, typename E > +T*& operator >>=( T*& lhs, SafeInt< U, E > rhs ) +{ + (rhs); + // This operator explicitly not supported + C_ASSERT( sizeof(T) == 0 ); + return (lhs = NULL); +} + +// Shift operators +// NOTE - shift operators always return the type of the lhs argument + +// Left shift +template < typename T, typename U, typename E > +SafeInt< U, E > operator <<( U lhs, SafeInt< T, E > bits ) throw() +{ + ShiftAssert( !IntTraits< T >::isSigned || (T)bits >= 0 ); + ShiftAssert( (T)bits < (int)IntTraits< U >::bitCount ); + + return SafeInt< U, E >( (U)( lhs << (T)bits ) ); +} + +// Right shift +template < typename T, typename U, typename E > +SafeInt< U, E > operator >>( U lhs, SafeInt< T, E > bits ) throw() +{ + ShiftAssert( !IntTraits< T >::isSigned || (T)bits >= 0 ); + ShiftAssert( (T)bits < (int)IntTraits< U >::bitCount ); + + return SafeInt< U, E >( (U)( lhs >> (T)bits ) ); +} + +// Bitwise operators +// This only makes sense if we're dealing with the same type and size +// demand a type T, or something that fits into a type T. + +// Bitwise & +template < typename T, typename U, typename E > +SafeInt< T, E > operator &( U lhs, SafeInt< T, E > rhs ) throw() +{ + return SafeInt< T, E >( BinaryAndHelper< T, U, BinaryMethod< T, U >::method >::And( (T)rhs, lhs ) ); +} + +// Bitwise XOR +template < typename T, typename U, typename E > +SafeInt< T, E > operator ^( U lhs, SafeInt< T, E > rhs ) throw() +{ + return SafeInt< T, E >(BinaryXorHelper< T, U, BinaryMethod< T, U >::method >::Xor( (T)rhs, lhs ) ); +} + +// Bitwise OR +template < typename T, typename U, typename E > +SafeInt< T, E > operator |( U lhs, SafeInt< T, E > rhs ) throw() +{ + return SafeInt< T, E >( BinaryOrHelper< T, U, BinaryMethod< T, U >::method >::Or( (T)rhs, lhs ) ); +} + +#endif //SAFEINT_HPP diff --git a/include/odata/common/compat/apple_compat.h b/include/odata/common/compat/apple_compat.h new file mode 100644 index 0000000..61bac4c --- /dev/null +++ b/include/odata/common/compat/apple_compat.h @@ -0,0 +1,83 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once +#include +#include +#include +#include +#define __cdecl + +#include "nosal.h" + +// MSVC doesn't support this yet +#define _noexcept noexcept + +#define novtable /* no novtable equivalent */ +#define __declspec(x) __attribute__ ((x)) +#define __export __attribute__ ((dllexport)) +#define __stdcall __attribute__ ((stdcall)) +#define STDMETHODCALLTYPE __export __stdcall + +// ignore these: +#define dllimport + +// OpenProt defines +#define _SH_DENYRW 0x20 + +#include + +#define __assume(x) do { if (!(x)) __builtin_unreachable(); } while (false) + +typedef uint32_t HRESULT; + +#define SOCKET int +#define SOCKET_ERROR -1 + +#define S_OK 0 +#define S_FALSE 1 +#define STG_E_CANTSAVE 0x80030103 +#define STG_E_INVALIDPOINTER 0x80030009 +#define E_NOTIMPL 0x80004001 +#define E_NOINTERFACE 0x80004002 + +typedef unsigned long ULONG; +typedef unsigned short WORD; +typedef unsigned long DWORD; + +typedef struct _SYSTEMTIME { + WORD wYear; + WORD wMonth; + WORD wDayOfWeek; + WORD wDay; + WORD wHour; + WORD wMinute; + WORD wSecond; + WORD wMilliseconds; +} SYSTEMTIME, *PSYSTEMTIME; + +#define ULARGE_INTEGER uint64_t +#define LARGE_INTEGER int64_t + +#define WINAPI __stdcall + +#define YieldProcessor() __asm__ __volatile__ ("pause") + +#define UNREFERENCED_PARAMETER(x) (void)x +#define _ASSERTE(x) assert(x) + +#include + +typedef char16_t utf16char; +typedef std::u16string utf16string; +typedef std::basic_stringstream utf16stringstream; +typedef std::basic_ostringstream utf16ostringstream; +typedef std::basic_ostream utf16ostream; +typedef std::basic_istream utf16istream; +typedef std::basic_istringstream utf16istringstream; + +#include "odata/common/compat/SafeInt3.hpp" +typedef SafeInt SafeSize; diff --git a/include/odata/common/compat/linux_compat.h b/include/odata/common/compat/linux_compat.h new file mode 100644 index 0000000..da0ce97 --- /dev/null +++ b/include/odata/common/compat/linux_compat.h @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once +#include +#include +#include +#include +#define __cdecl __attribute__ ((cdecl)) + +#include "nosal.h" + +// MSVC doesn't support this yet +#define _noexcept noexcept + +#define novtable /* no novtable equivalent */ +#define __declspec(x) __attribute__ ((x)) +#define __export __attribute__ ((dllexport)) +#define __stdcall __attribute__ ((stdcall)) +#define STDMETHODCALLTYPE __export __stdcall + +// ignore these: +#define dllimport +#ifdef __LP64__ // ignore cdecl on 64-bit +#define cdecl +#endif + +// OpenProt defines +#define _SH_DENYRW 0x20 + +#include + +#define __assume(x) do { if (!(x)) __builtin_unreachable(); } while (false) + +typedef uint32_t HRESULT; + +#define SOCKET int +#define SOCKET_ERROR -1 + +#define S_OK 0 +#define S_FALSE 1 +#define STG_E_CANTSAVE 0x80030103 +#define STG_E_INVALIDPOINTER 0x80030009 +#define E_NOTIMPL 0x80004001 +#define E_NOINTERFACE 0x80004002 + +#define ULARGE_INTEGER uint64_t +#define LARGE_INTEGER int64_t + +#define WINAPI __stdcall + +#define YieldProcessor() __asm__ __volatile__ ("pause") + +#define UNREFERENCED_PARAMETER(x) (void)x +#define _ASSERTE(x) assert(x) + +#include + +typedef char16_t utf16char; +typedef std::u16string utf16string; +typedef std::basic_stringstream utf16stringstream; +typedef std::basic_ostringstream utf16ostringstream; +typedef std::basic_ostream utf16ostream; +typedef std::basic_istream utf16istream; +typedef std::basic_istringstream utf16istringstream; + +#include "SafeInt3.hpp" +typedef SafeInt SafeSize; + diff --git a/include/odata/common/compat/nosal.h b/include/odata/common/compat/nosal.h new file mode 100644 index 0000000..f8f98b5 --- /dev/null +++ b/include/odata/common/compat/nosal.h @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once +// selected MS SAL annotations + +#ifdef _In_ +#undef _In_ +#endif +#define _In_ + +#ifdef _Inout_ +#undef _Inout_ +#endif +#define _Inout_ + +#ifdef _Out_ +#undef _Out_ +#endif +#define _Out_ + +#ifdef _In_z_ +#undef _In_z_ +#endif +#define _In_z_ + +#ifdef _Out_z_ +#undef _Out_z_ +#endif +#define _Out_z_ + +#ifdef _Inout_z_ +#undef _Inout_z_ +#endif +#define _Inout_z_ + +#ifdef _In_opt_ +#undef _In_opt_ +#endif +#define _In_opt_ + +#ifdef _Out_opt_ +#undef _Out_opt_ +#endif +#define _Out_opt_ + +#ifdef _Inout_opt_ +#undef _Inout_opt_ +#endif +#define _Inout_opt_ + +#ifdef _Out_writes_ +#undef _Out_writes_ +#endif +#define _Out_writes_(x) + +#ifdef _Out_writes_opt_ +#undef _Out_writes_opt_ +#endif +#define _Out_writes_opt_(x) + +#ifdef _In_reads_ +#undef _In_reads_ +#endif +#define _In_reads_(x) + +#ifdef _Inout_updates_bytes_ +#undef _Inout_updates_bytes_ +#endif +#define _Inout_updates_bytes_(x) \ No newline at end of file diff --git a/include/odata/common/compat/windows_compat.h b/include/odata/common/compat/windows_compat.h new file mode 100644 index 0000000..e81f03b --- /dev/null +++ b/include/odata/common/compat/windows_compat.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata\common\compat\safeint3.hpp" +#include + +#if _MSC_VER >= 1700 +// Support VS2012 SAL syntax only +#include +#else +#include "odata/common/compat/nosal.h" +#endif + +#define _noexcept + +typedef wchar_t utf16char; +typedef std::wstring utf16string; +typedef std::wstringstream utf16stringstream; +typedef std::wostringstream utf16ostringstream; +typedef std::wostream utf16ostream; +typedef std::wistream utf16istream; +typedef std::wistringstream utf16istringstream; + +#define _XPLATSTR(x) L ## x + +#ifndef _TURN_OFF_PLATFORM_STRING +#define U(x) _XPLATSTR(x) +#endif // !_TURN_OFF_PLATFORM_STRING + +typedef SafeInt SafeSize; \ No newline at end of file diff --git a/include/odata/common/json.h b/include/odata/common/json.h new file mode 100644 index 0000000..1cb62d4 --- /dev/null +++ b/include/odata/common/json.h @@ -0,0 +1,1741 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#ifndef _ODATA_JSON_H +#define _ODATA_JSON_H + +#include +#include +#include +#include +#include +#include +#include +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" + +namespace odata { namespace utility { namespace json +{ + + // Various forward declarations. + namespace details + { + class _Value; + class _Number; + class _Null; + class _Boolean; + class _String; + class _Object; + class _Array; + template class JSON_Parser; + } + + namespace details + { + extern bool g_keep_json_object_unsorted; + } + + /// + /// Preserve the order of the name/value pairs when parsing a JSON object + /// + void ODATACPP_API keep_object_element_order(bool keep_order); + +#ifdef _MS_WINDOWS +#ifdef _DEBUG +#define ENABLE_JSON_VALUE_VISUALIZER +#endif +#endif + + class number; + class array; + class object; + + /// + /// A JSON value represented as a C++ class. + /// + class value + { + public: + /// + /// This enumeration represents the various kinds of JSON values. + /// + enum value_type { Number, Boolean, String, Object, Array, Null }; + + /// + /// Constructor creating a null value + /// + ODATACPP_API value(); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + ODATACPP_API value(int32_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + ODATACPP_API value(uint32_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + ODATACPP_API value(int64_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + ODATACPP_API value(uint64_t value); + + /// + /// Constructor creating a JSON number value + /// + /// The C++ value to create a JSON value from + ODATACPP_API value(double value); + + /// + /// Constructor creating a JSON Boolean value + /// + /// The C++ value to create a JSON value from + ODATACPP_API explicit value(bool value); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character width + ODATACPP_API explicit value(utility::string_t); + + /// + /// Constructor creating a JSON string value + /// + /// The C++ value to create a JSON value from, a C++ STL string of the platform-native character width + /// This constructor exists in order to avoid string literals matching another constructor, + /// as is very likely. For example, conversion to bool does not require a user-defined conversion, + /// and will therefore match first, which means that the JSON value turns up as a boolean. + ODATACPP_API explicit value(const utility::char_t *); + + /// + /// Copy constructor + /// + ODATACPP_API value(const value &); + + /// + /// Move constructor + /// + ODATACPP_API value(value &&); + + /// + /// Assignment operator. + /// + /// The JSON value object that contains the result of the assignment. + ODATACPP_API value &operator=(const value &); + + /// + /// Move assignment operator. + /// + /// The JSON value object that contains the result of the assignment. + ODATACPP_API value &operator=(value &&); + + // Static factories + + /// + /// Creates a null value + /// + /// A JSON null value + static ODATACPP_API value __cdecl null(); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static ODATACPP_API value __cdecl number(double value); + + /// + /// Creates a number value + /// + /// The C++ value to create a JSON value from + /// A JSON number value + static ODATACPP_API value __cdecl number(int32_t value); + + /// + /// Creates a Boolean value + /// + /// The C++ value to create a JSON value from + /// A JSON Boolean value + static ODATACPP_API value __cdecl boolean(bool value); + + /// + /// Creates a string value + /// + /// The C++ value to create a JSON value from + /// A JSON string value + static ODATACPP_API value __cdecl string(utility::string_t value); + +#ifdef _MS_WINDOWS +private: + // Only used internally by JSON parser. + static ODATACPP_API value __cdecl string(const std::string &value); +public: +#endif + + /// + /// Creates an object value + /// + /// Whether to preserve the original order of the fields + /// An empty JSON object value + static ODATACPP_API json::value __cdecl object(bool keep_order = false); + + /// + /// Creates an object value from a collection of field/values + /// + /// Field names associated with JSON values + /// Whether to preserve the original order of the fields + /// A non-empty JSON object value + static ODATACPP_API json::value __cdecl object(std::vector> fields, bool keep_order = false); + + /// + /// Creates an empty JSON array + /// + /// An empty JSON array value + static ODATACPP_API json::value __cdecl array(); + + /// + /// Creates a JSON array + /// + /// The initial number of elements of the JSON value + /// A JSON array value + static ODATACPP_API json::value __cdecl array(size_t size); + + /// + /// Creates a JSON array + /// + /// A vector of JSON values + /// A JSON array value + static ODATACPP_API json::value __cdecl array(std::vector elements); + + /// + /// Accesses the type of JSON value the current value instance is + /// + /// The value's type + ODATACPP_API json::value::value_type __cdecl type() const; + + /// + /// Is the current value a null value? + /// + /// true if the value is a null value, false otherwise + bool is_null() const { return type() == Null; }; + + /// + /// Is the current value a number value? + /// + /// true if the value is a number value, false otherwise + bool is_number() const { return type() == Number; } + + /// + /// Is the current value represented as an integer number value? + /// + /// + /// Note that if a json value is a number but represented as a double it can still + /// be retrieved as a integer using as_integer(), however the value will be truncated. + /// + /// true if the value is an integer value, false otherwise. + ODATACPP_API bool is_integer() const; + + /// + /// Is the current value represented as an double number value? + /// + /// + /// Note that if a json value is a number but represented as a int it can still + /// be retrieved as a double using as_double(). + /// + /// true if the value is an double value, false otherwise. + ODATACPP_API bool is_double() const; + + /// + /// Is the current value a Boolean value? + /// + /// true if the value is a Boolean value, false otherwise + bool is_boolean() const { return type() == Boolean; } + + /// + /// Is the current value a string value? + /// + /// true if the value is a string value, false otherwise + bool is_string() const { return type() == String; } + + /// + /// Is the current value an array? + /// + /// true if the value is an array, false otherwise + bool is_array() const { return type() == Array; } + + /// + /// Is the current value an object? + /// + /// true if the value is an object, false otherwise + bool is_object() const { return type() == Object; } + + /// + /// Gets the number of children of the value. + /// + /// The number of children. 0 for all non-composites. + size_t size() const; + + /// + /// Parses a string and construct a JSON value. + /// + /// The C++ value to create a JSON value from, a C++ STL double-byte string + ODATACPP_API static value parse(const utility::string_t&); + + /// + /// Serializes the current JSON value to a C++ string. + /// + /// A string representation of the value + ODATACPP_API utility::string_t serialize() const; + + /// + /// Serializes the current JSON value to a C++ string. + /// + /// A string representation of the value + ODATACPP_API utility::string_t to_string() const; + + /// + /// Parses a JSON value from the contents of an input stream using the native platform character width. + /// + /// The stream to read the JSON value from + /// The JSON value object created from the input stream. + ODATACPP_API static value parse(utility::istream_t &input); + + /// + /// Writes the current JSON value to a stream with the native platform character width. + /// + /// The stream that the JSON string representation should be written to. + ODATACPP_API void serialize(utility::ostream_t &stream) const; + +#ifdef _MS_WINDOWS + /// + /// Parses a JSON value from the contents of a single-byte (UTF8) stream. + /// + /// The stream to read the JSON value from + ODATACPP_API static value parse(std::istream& stream); + + /// + /// Serializes the content of the value into a single-byte (UTF8) stream. + /// + /// The stream that the JSON string representation should be written to. + ODATACPP_API void serialize(std::ostream& stream) const; +#endif + + /// + /// Converts the JSON value to a C++ double, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// A double representation of the value + ODATACPP_API double as_double() const; + + /// + /// Converts the JSON value to a C++ integer, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// An integer representation of the value + ODATACPP_API int as_integer() const; + + /// + /// Converts the JSON value to a number class, if and only if it is a number value. + /// Throws if the value is not a number + /// + /// An instance of number class + ODATACPP_API json::number as_number() const; + + /// + /// Converts the JSON value to a C++ bool, if and only if it is a Boolean value. + /// + /// A C++ bool representation of the value + ODATACPP_API bool as_bool() const; + + /// + /// Converts the JSON value to a json array, if and only if it is an array value. + /// + /// The returned json::array should have the same or shorter lifetime as this + /// An array representation of the value + ODATACPP_API json::array& as_array(); + + /// + /// Converts the JSON value to a json array, if and only if it is an array value. + /// + /// The returned json::array should have the same or shorter lifetime as this + /// An array representation of the value + ODATACPP_API const json::array& as_array() const; + + /// + /// Converts the JSON value to a json object, if and only if it is an object value. + /// + /// An object representation of the value + ODATACPP_API json::object& as_object(); + + /// + /// Converts the JSON value to a json object, if and only if it is an object value. + /// + /// An object representation of the value + ODATACPP_API const json::object& as_object() const; + + /// + /// Converts the JSON value to a C++ STL string, if and only if it is a string value. + /// + /// A C++ STL string representation of the value + ODATACPP_API utility::string_t as_string() const; + + /// + /// Compares two JSON values for equality. + /// + /// The JSON value to compare with. + /// True iff the values are equal. + ODATACPP_API bool operator==(const value& other) const; + + /// + /// Compares two JSON values for inequality. + /// + /// The JSON value to compare with. + /// True iff the values are unequal. + bool operator!=(const value& other) const + { + return !((*this) == other); + } + + /// + /// Tests for the presence of a field. + /// + /// The name of the field + /// True if the field exists, false otherwise. + bool has_field(const utility::string_t &key) const; + + /// + /// Accesses a field of a JSON object. + /// + /// The name of the field + /// The value kept in the field; null if the field does not exist + value get(const utility::string_t &key) const; + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value. + ODATACPP_API json::value& at(size_t index); + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value. + ODATACPP_API const json::value& at(size_t index) const; + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value. + ODATACPP_API json::value& at(const utility::string_t& key); + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value. + ODATACPP_API const json::value& at(const utility::string_t& key) const; + + /// + /// Accesses a field of a JSON object. + /// + /// The name of the field + /// A reference to the value kept in the field. + ODATACPP_API value & operator [] (const utility::string_t &key); + +#ifdef _MS_WINDOWS +private: + // Only used internally by JSON parser + ODATACPP_API value & operator [] (const std::string &key) + { + // JSON object stores its field map as a unordered_map of string_t, so this conversion is hard to avoid + return operator[](utility::conversions::to_string_t(key)); + } +public: +#endif + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array + /// The value kept at the array index; null if outside the boundaries of the array + value get(size_t index) const; + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + ODATACPP_API value & operator [] (size_t index); + + private: + friend class odata::utility::json::details::_Object; + friend class odata::utility::json::details::_Array; + template friend class odata::utility::json::details::JSON_Parser; + +#ifdef _MS_WINDOWS + /// + /// Writes the current JSON value as a double-byte string to a string instance. + /// + /// The string that the JSON representation should be written to. + ODATACPP_API void format(std::basic_string &string) const; +#endif + /// + /// Serializes the content of the value into a string instance in UTF8 format + /// + /// The string that the JSON representation should be written to + ODATACPP_API void format(std::basic_string& string) const; + +#ifdef ENABLE_JSON_VALUE_VISUALIZER + explicit value(std::unique_ptr v, value_type kind) : m_value(std::move(v)), m_kind(kind) +#else + explicit value(std::unique_ptr v) : m_value(std::move(v)) +#endif + {} + + std::unique_ptr m_value; +#ifdef ENABLE_JSON_VALUE_VISUALIZER + value_type m_kind; +#endif + }; + + /// + /// A single exception type to represent errors in parsing, converting, and accessing + /// elements of JSON values. + /// + class json_exception : public std::exception + { + private: + std::string _message; + public: + json_exception() {} + json_exception(const utility::char_t * const &message) : _message(utility::conversions::to_utf8string(message)) { } + + // Must be narrow string because it derives from std::exception + const char* what() const _noexcept + { + return _message.c_str(); + } + ~json_exception() _noexcept {} + }; + + /// + /// A JSON array represented as a C++ class. + /// + class array + { + typedef std::vector storage_type; + + public: + typedef storage_type::iterator iterator; + typedef storage_type::const_iterator const_iterator; + typedef storage_type::reverse_iterator reverse_iterator; + typedef storage_type::const_reverse_iterator const_reverse_iterator; + typedef storage_type::size_type size_type; + + private: + array() : m_elements() { } + array(size_type size) : m_elements(size) { } + array(storage_type elements) : m_elements(std::move(elements)) { } + + public: + /// + /// Gets the beginning iterator element of the array + /// + /// An iterator to the beginning of the JSON array. + iterator begin() + { + return m_elements.begin(); + } + + /// + /// Gets the beginning const iterator element of the array. + /// + /// A const_iterator to the beginning of the JSON array. + const_iterator begin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end iterator element of the array + /// + /// An iterator to the end of the JSON array. + iterator end() + { + return m_elements.end(); + } + + /// + /// Gets the end const iterator element of the array. + /// + /// A const_iterator to the end of the JSON array. + const_iterator end() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning reverse iterator element of the array + /// + /// An reverse_iterator to the beginning of the JSON array. + reverse_iterator rbegin() + { + return m_elements.rbegin(); + } + + /// + /// Gets the beginning const reverse iterator element of the array + /// + /// An const_reverse_iterator to the beginning of the JSON array. + const_reverse_iterator rbegin() const + { + return m_elements.rbegin(); + } + + /// + /// Gets the end reverse iterator element of the array + /// + /// An reverse_iterator to the end of the JSON array. + reverse_iterator rend() + { + return m_elements.rend(); + } + + /// + /// Gets the end const reverse iterator element of the array + /// + /// An const_reverse_iterator to the end of the JSON array. + const_reverse_iterator rend() const + { + return m_elements.crend(); + } + + /// + /// Gets the beginning const iterator element of the array. + /// + /// A const_iterator to the beginning of the JSON array. + const_iterator cbegin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end const iterator element of the array. + /// + /// A const_iterator to the end of the JSON array. + const_iterator cend() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning const reverse iterator element of the array. + /// + /// A const_reverse_iterator to the beginning of the JSON array. + const_reverse_iterator crbegin() const + { + return m_elements.crbegin(); + } + + /// + /// Gets the end const reverse iterator element of the array. + /// + /// A const_reverse_iterator to the end of the JSON array. + const_reverse_iterator crend() const + { + return m_elements.crend(); + } + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + json::value& at(size_type index) + { + if (index >= m_elements.size()) + throw json_exception(_XPLATSTR("index out of bounds")); + + return m_elements[index]; + } + + /// + /// Accesses an element of a JSON array. Throws when index out of bounds. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + const json::value& at(size_type index) const + { + if (index >= m_elements.size()) + throw json_exception(_XPLATSTR("index out of bounds")); + + return m_elements[index]; + } + + /// + /// Accesses an element of a JSON array. + /// + /// The index of an element in the JSON array. + /// A reference to the value kept in the field. + json::value& operator[](size_type index) + { + SafeInt nMinSize(index); + nMinSize += 1; + SafeInt nlastSize(m_elements.size()); + if (nlastSize < nMinSize) + m_elements.resize(nMinSize); + + return m_elements[index]; + } + + /// + /// Gets the number of elements of the array. + /// + /// The number of elements. + size_type size() const + { + return m_elements.size(); + } + + private: + storage_type m_elements; + + friend class details::_Array; + template friend class json::details::JSON_Parser; + }; + + /// + /// A JSON object represented as a C++ class. + /// + class object + { + typedef std::vector> storage_type; + + public: + typedef storage_type::iterator iterator; + typedef storage_type::const_iterator const_iterator; + typedef storage_type::reverse_iterator reverse_iterator; + typedef storage_type::const_reverse_iterator const_reverse_iterator; + typedef storage_type::size_type size_type; + + private: + object(bool keep_order = false) : m_elements(), m_keep_order(keep_order) { } + object(storage_type elements, bool keep_order = false) : m_elements(std::move(elements)), m_keep_order(keep_order) + { + if (!keep_order) { + sort(m_elements.begin(), m_elements.end(), compare_pairs); + } + } + object(const object& obj); // non copyable + object& operator=(const object& obj); // non copyable + + public: + /// + /// Gets the beginning iterator element of the object + /// + /// An iterator to the beginning of the JSON object. + iterator begin() + { + return m_elements.begin(); + } + + /// + /// Gets the beginning const iterator element of the object. + /// + /// A const_iterator to the beginning of the JSON object. + const_iterator begin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end iterator element of the object + /// + /// An iterator to the end of the JSON object. + iterator end() + { + return m_elements.end(); + } + + /// + /// Gets the end const iterator element of the object. + /// + /// A const_iterator to the end of the JSON object. + const_iterator end() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning reverse iterator element of the object + /// + /// An reverse_iterator to the beginning of the JSON object. + reverse_iterator rbegin() + { + return m_elements.rbegin(); + } + + /// + /// Gets the beginning const reverse iterator element of the object + /// + /// An const_reverse_iterator to the beginning of the JSON object. + const_reverse_iterator rbegin() const + { + return m_elements.rbegin(); + } + + /// + /// Gets the end reverse iterator element of the object + /// + /// An reverse_iterator to the end of the JSON object. + reverse_iterator rend() + { + return m_elements.rend(); + } + + /// + /// Gets the end const reverse iterator element of the object + /// + /// An const_reverse_iterator to the end of the JSON object. + const_reverse_iterator rend() const + { + return m_elements.crend(); + } + + /// + /// Gets the beginning const iterator element of the object. + /// + /// A const_iterator to the beginning of the JSON object. + const_iterator cbegin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end const iterator element of the object. + /// + /// A const_iterator to the end of the JSON object. + const_iterator cend() const + { + return m_elements.cend(); + } + + /// + /// Gets the beginning const reverse iterator element of the object. + /// + /// A const_reverse_iterator to the beginning of the JSON object. + const_reverse_iterator crbegin() const + { + return m_elements.crbegin(); + } + + /// + /// Gets the end const reverse iterator element of the object. + /// + /// A const_reverse_iterator to the end of the JSON object. + const_reverse_iterator crend() const + { + return m_elements.crend(); + } + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field. + json::value& at(const utility::string_t& key) + { + auto iter = find_by_key(key); + + if (iter == m_elements.end() || key != (iter->first)) + throw odata::utility::json::json_exception(_XPLATSTR("Key not found")); + + return iter->second; + } + + /// + /// Accesses an element of a JSON object. If the key doesn't exist, this method throws. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field. + const json::value& at(const utility::string_t& key) const + { + auto iter = find_by_key(key); + + if (iter == m_elements.end() || key != (iter->first)) + throw odata::utility::json::json_exception(_XPLATSTR("Key not found")); + + return iter->second; + } + + /// + /// Accesses an element of a JSON object. + /// + /// The key of an element in the JSON object. + /// If the key exists, a reference to the value kept in the field, otherwise a newly created null value that will be stored for the given key. + json::value& operator[](const utility::string_t& key) + { + auto iter = find_by_key(key); + + if (iter == m_elements.end() || key != (iter->first)) + return m_elements.insert(iter, std::pair(key, value()))->second; + + return iter->second; + } + + /// + /// Gets an iterator to an element of a JSON object. + /// + /// The key of an element in the JSON object. + /// A const iterator to the value kept in the field. + const_iterator find(const utility::string_t& key) const + { + return find_internal(key); + } + + /// + /// Gets an iterator to an element of a JSON object. + /// + /// The key of an element in the JSON object. + /// An iterator to the value kept in the field. + iterator find(const utility::string_t& key) + { + return utility::details::remove_iterator_constness(m_elements, find_internal(key)); + } + + /// + /// Gets the number of elements of the object. + /// + /// The number of elements. + size_type size() const + { + return m_elements.size(); + } + + /// + /// Checks if there are any elements in the JSON object. + /// + /// True iff empty. + bool empty() const + { + return m_elements.empty(); + } + private: + + static bool compare_pairs(const std::pair& p1, const std::pair& p2) + { + return p1.first < p2.first; + } + static bool compare_with_key(const std::pair& p1, const utility::string_t& key) + { + return p1.first < key; + } + + storage_type::const_iterator find_by_key(const utility::string_t& key) const + { + if (m_keep_order) + { + return std::find_if(m_elements.begin(), m_elements.end(), + [&key](const std::pair& p) { + return p.first == key; + }); + } + else + { + return std::lower_bound(m_elements.begin(), m_elements.end(), key, compare_with_key); + } + } + + storage_type::iterator find_by_key(const utility::string_t& key) + { + if (m_keep_order) + { + return std::find_if(m_elements.begin(), m_elements.end(), + [&key](const std::pair& p) { + return p.first == key; + }); + } + else + { + return std::lower_bound(m_elements.begin(), m_elements.end(), key, compare_with_key); + } + } + + const json::value& at_internal(const utility::string_t& key) const + { + auto iter = find_by_key(key); + + if (iter == m_elements.end() || key != (iter->first)) + throw odata::utility::json::json_exception(_XPLATSTR("Key not found")); + + return iter->second; + } + + const_iterator find_internal(const utility::string_t& key) const + { + auto iter = find_by_key(key); + + if (iter != m_elements.end() && key != (iter->first)) + return m_elements.end(); + + return iter; + } + + iterator find_internal(const utility::string_t& key) + { + auto iter = find_by_key(key); + + if (iter != m_elements.end() && key != (iter->first)) + return m_elements.end(); + + return iter; + } + + const bool m_keep_order; + storage_type m_elements; + friend class details::_Object; + + template friend class json::details::JSON_Parser; + }; + + /// + /// A JSON number represented as a C++ class. + /// + class number + { + // Note that these constructors make sure that only negative integers are stored as signed int64 (while others convert to unsigned int64). + // This helps handling number objects e.g. comparing two numbers. + + number(double value) : m_value(value), m_type(double_type) { } + number(int32_t value) : m_intval(value), m_type(value < 0 ? signed_type : unsigned_type) { } + number(uint32_t value) : m_intval(value), m_type(unsigned_type) { } + number(int64_t value) : m_intval(value), m_type(value < 0 ? signed_type : unsigned_type) { } + number(uint64_t value) : m_uintval(value), m_type(unsigned_type) { } + + public: + +#pragma region "is" checkers + /// + /// Does the number fit into int32? + /// + /// true if the number fits into int32, false otherwise + ODATACPP_API bool is_int32() const; + + /// + /// Does the number fit into unsigned int32? + /// + /// true if the number fits into unsigned int32, false otherwise + ODATACPP_API bool is_uint32() const; + + /// + /// Does the number fit into int64? + /// + /// true if the number fits into int64, false otherwise + ODATACPP_API bool is_int64() const; + + /// + /// Does the number fit into unsigned int64? + /// + /// true if the number fits into unsigned int64, false otherwise + bool is_uint64() const + { + switch (m_type) + { + case signed_type : return m_intval >= 0; + case unsigned_type : return true; + case double_type : + default : + return false; + } + } +#pragma endregion + +#pragma region "to" converters + /// + /// Converts the JSON number to a C++ double. + /// + /// A double representation of the number + double to_double() const + { + switch (m_type) + { + case double_type : return m_value; + case signed_type : return static_cast(m_intval); + case unsigned_type : return static_cast(m_uintval); + default : return false; + } + } + + /// + /// Converts the JSON number to int32. + /// + /// An int32 representation of the number + int32_t to_int32() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to unsigned int32. + /// + /// An usigned int32 representation of the number + uint32_t to_uint32() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to int64. + /// + /// An int64 representation of the number + int64_t to_int64() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } + + /// + /// Converts the JSON number to unsigned int64. + /// + /// An unsigned int64 representation of the number + uint64_t to_uint64() const + { + if (m_type == double_type) + return static_cast(m_value); + else + return static_cast(m_intval); + } +#pragma endregion + + /// + /// Is the number represented internally as an integral type? + /// + /// true if the number is represented as an integral type, false otherwise + bool is_integral() const + { + return m_type != double_type; + } + + /// + /// Compares two JSON numbers for equality. + /// + /// The JSON number to compare with. + /// True iff the numbers are equal. + bool operator==(const number &other) const + { + if (m_type != other.m_type) + return false; + + switch (m_type) + { + case json::number::type::signed_type : + return m_intval == other.m_intval; + case json::number::type::unsigned_type : + return m_uintval == other.m_uintval; + case json::number::type::double_type : + return m_value == other.m_value; + } + UNREACHABLE; + } + + private: + union + { + int64_t m_intval; + uint64_t m_uintval; + double m_value; + }; + + enum type + { + signed_type=0, unsigned_type, double_type + } m_type; + + friend class details::_Number; + }; + + namespace details + { + class _Value + { + public: + virtual std::unique_ptr<_Value> _copy_value() = 0; + + virtual bool has_field(const utility::string_t &) const { return false; } + virtual value get_field(const utility::string_t &) const { throw json_exception(_XPLATSTR("not an object")); } + virtual value get_element(array::size_type) const { throw json_exception(_XPLATSTR("not an array")); } + + virtual value &index(const utility::string_t &) { throw json_exception(_XPLATSTR("not an object")); } + virtual value &index(array::size_type) { throw json_exception(_XPLATSTR("not an array")); } + + virtual const value &cnst_index(const utility::string_t &) const { throw json_exception(_XPLATSTR("not an object")); } + virtual const value &cnst_index(array::size_type) const { throw json_exception(_XPLATSTR("not an array")); } + + // Common function used for serialization to strings and streams. + virtual void serialize_impl(std::string& str) const + { + format(str); + } +#ifdef _MS_WINDOWS + virtual void serialize_impl(std::wstring& str) const + { + format(str); + } +#endif + + virtual utility::string_t to_string() const + { + utility::string_t str; + serialize_impl(str); + return str; + } + + virtual json::value::value_type type() const { return json::value::Null; } + + virtual bool is_integer() const { throw json_exception(_XPLATSTR("not a number")); } + virtual bool is_double() const { throw json_exception(_XPLATSTR("not a number")); } + + virtual json::number as_number() { throw json_exception(_XPLATSTR("not a number")); } + virtual double as_double() const { throw json_exception(_XPLATSTR("not a number")); } + virtual int as_integer() const { throw json_exception(_XPLATSTR("not a number")); } + virtual bool as_bool() const { throw json_exception(_XPLATSTR("not a boolean")); } + virtual json::array& as_array() { throw json_exception(_XPLATSTR("not an array")); } + virtual const json::array& as_array() const { throw json_exception(_XPLATSTR("not an array")); } + virtual json::object& as_object() { throw json_exception(_XPLATSTR("not an object")); } + virtual const json::object& as_object() const { throw json_exception(_XPLATSTR("not an object")); } + virtual utility::string_t as_string() const { throw json_exception(_XPLATSTR("not a string")); } + + virtual size_t size() const { return 0; } + + virtual ~_Value() {} + + protected: + _Value() {} + + virtual void format(std::basic_string& stream) const + { + stream.append("null"); + } +#ifdef _MS_WINDOWS + virtual void format(std::basic_string& stream) const + { + stream.append(L"null"); + } +#endif + private: + + friend class odata::utility::json::value; + }; + + class _Null : public _Value + { + public: + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Null>(); + } + + virtual json::value::value_type type() const { return json::value::Null; } + + _Null() { } + + private: + template friend class json::details::JSON_Parser; + }; + + class _Number : public _Value + { + public: + _Number(double value) : m_number(value) { } + _Number(int32_t value) : m_number(value) { } + _Number(uint32_t value) : m_number(value) { } + _Number(int64_t value) : m_number(value) { } + _Number(uint64_t value) : m_number(value) { } + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Number>(*this); + } + + virtual json::value::value_type type() const { return json::value::Number; } + + virtual bool is_integer() const { return m_number.is_integral(); } + virtual bool is_double() const { return !m_number.is_integral(); } + + virtual double as_double() const + { + return m_number.to_double(); + } + + virtual int as_integer() const + { + return m_number.to_int32(); + } + + virtual number as_number() { return m_number; } + + protected: + virtual void format(std::basic_string& stream) const ; +#ifdef _MS_WINDOWS + virtual void format(std::basic_string& stream) const; +#endif + private: + template friend class json::details::JSON_Parser; + + json::number m_number; + }; + + class _Boolean : public _Value + { + public: + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Boolean>(*this); + } + + virtual json::value::value_type type() const { return json::value::Boolean; } + + virtual bool as_bool() const { return m_value; } + + protected: + virtual void format(std::basic_string& stream) const + { + stream.append(m_value ? "true" : "false"); + } + +#ifdef _MS_WINDOWS + virtual void format(std::basic_string& stream) const + { + stream.append(m_value ? L"true" : L"false"); + } +#endif + private: + template friend class json::details::JSON_Parser; + public: + _Boolean(bool value) : m_value(value) { } + private: + bool m_value; + }; + + class _String : public _Value + { + public: + + _String(utility::string_t value) : m_string(std::move(value)) + { + m_has_escape_char = has_escape_chars(*this); + } + _String(utility::string_t value, bool escaped_chars) + : m_string(std::move(value)), + m_has_escape_char(escaped_chars) + { } + +#ifdef _MS_WINDOWS + _String(std::string &&value) : m_string(utility::conversions::to_utf16string(std::move(value))) + { + m_has_escape_char = has_escape_chars(*this); + } + _String(std::string &&value, bool escape_chars) + : m_string(utility::conversions::to_utf16string(std::move(value))), + m_has_escape_char(escape_chars) + { } +#endif + + _String(const _String& other) : odata::utility::json::details::_Value(other) + { + copy_from(other); + } + + _String& operator=(const _String& other) + { + if (this != &other) { + copy_from(other); + } + return *this; + } + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_String>(*this); + } + + virtual json::value::value_type type() const { return json::value::String; } + + virtual utility::string_t as_string() const; + + virtual void serialize_impl(std::string& str) const + { + serialize_impl_char_type(str); + } +#ifdef _MS_WINDOWS + virtual void serialize_impl(std::wstring& str) const + { + serialize_impl_char_type(str); + } + +#endif + + protected: + virtual void format(std::basic_string& str) const; +#ifdef _MS_WINDOWS + virtual void format(std::basic_string& str) const; +#endif + + private: + friend class _Object; + friend class _Array; + + size_t get_reserve_size() const + { + return m_string.size() + 2; + } + + template + void serialize_impl_char_type(std::basic_string& str) const + { + // To avoid repeated allocations reserve some space all up front. + // size of string + 2 for quotes + str.reserve(get_reserve_size()); + format(str); + } + + void copy_from(const _String& other) + { + m_string = other.m_string; + m_has_escape_char = other.m_has_escape_char; + } + + std::string as_utf8_string() const; + utf16string as_utf16_string() const; + + utility::string_t m_string; + + // There are significant performance gains that can be made by knowning whether + // or not a character that requires escaping is present. + bool m_has_escape_char; + static bool has_escape_chars(const _String &str); + }; + + template + ODATACPP_API void append_escape_string(std::basic_string& str, const std::basic_string& escaped); + + void format_string(const utility::string_t& key, utility::string_t& str); + +#ifdef _MS_WINDOWS + void format_string(const utility::string_t& key, std::string& str); +#endif + + class _Object : public _Value + { + public: + + _Object(bool keep_order) : m_object(keep_order) { } + + _Object(object::storage_type fields, bool keep_order) : m_object(std::move(fields), keep_order) { } + + ODATACPP_API _Object(const _Object& other); + + virtual ~_Object() {} + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Object>(*this); + } + + virtual json::object& as_object() { return m_object; } + + virtual const json::object& as_object() const { return m_object; } + + virtual json::value::value_type type() const { return json::value::Object; } + + virtual bool has_field(const utility::string_t &) const; + + ODATACPP_API virtual json::value &index(const utility::string_t &key); + + bool is_equal(const _Object* other) const + { + if ( m_object.size() != other->m_object.size()) + return false; + + return std::equal(std::begin(m_object), std::end(m_object), std::begin(other->m_object)); + } + + virtual void serialize_impl(std::string& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#ifdef _MS_WINDOWS + virtual void serialize_impl(std::wstring& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#endif + size_t size() const { return m_object.size(); } + + protected: + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#ifdef _MS_WINDOWS + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#endif + + private: + json::object m_object; + + template friend class json::details::JSON_Parser; + + ODATACPP_API void map_fields(); + + template + void format_impl(std::basic_string& str) const + { + str.push_back('{'); + if(!m_object.empty()) + { + auto lastElement = m_object.end() - 1; + for (auto iter = m_object.begin(); iter != lastElement; ++iter) + { + format_string(iter->first, str); + str.push_back(':'); + iter->second.format(str); + str.push_back(','); + } + format_string(lastElement->first, str); + str.push_back(':'); + lastElement->second.format(str); + } + str.push_back('}'); + } + + size_t get_reserve_size() const + { + // This is a heuristic we can tune more in the future: + // Basically size of string plus + // sum size of value if an object, array, or string. + size_t reserveSize = 2; // For brackets {} + for(auto iter = m_object.begin(); iter != m_object.end(); ++iter) + { + reserveSize += iter->first.length() + 2; // 2 for quotes + size_t valueSize = iter->second.size() * 20; // Multipler by each object/array element + if(valueSize == 0) + { + if(iter->second.type() == json::value::String) + { + valueSize = static_cast<_String *>(iter->second.m_value.get())->get_reserve_size(); + } + else + { + valueSize = 5; // true, false, or null + } + } + reserveSize += valueSize; + } + return reserveSize; + } + }; + + class _Array : public _Value + { + public: + _Array() {} + _Array(array::size_type size) : m_array(size) {} + _Array(array::storage_type elements) : m_array(std::move(elements)) { } + + virtual std::unique_ptr<_Value> _copy_value() + { + return utility::details::make_unique<_Array>(*this); + } + + virtual json::value::value_type type() const { return json::value::Array; } + + virtual json::array& as_array() { return m_array; } + virtual const json::array& as_array() const { return m_array; } + + virtual json::value &index(json::array::size_type index) + { + return m_array[index]; + } + + bool is_equal(const _Array* other) const + { + if ( m_array.size() != other->m_array.size()) + return false; + + auto iterT = m_array.cbegin(); + auto iterO = other->m_array.cbegin(); + auto iterTe = m_array.cend(); + auto iterOe = other->m_array.cend(); + + for (; iterT != iterTe && iterO != iterOe; ++iterT, ++iterO) + { + if ( *iterT != *iterO ) + return false; + } + + return true; + } + + virtual void serialize_impl(std::string& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#ifdef _MS_WINDOWS + virtual void serialize_impl(std::wstring& str) const + { + // To avoid repeated allocations reserve some space all up front. + str.reserve(get_reserve_size()); + format(str); + } +#endif + size_t size() const { return m_array.size(); } + + protected: + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#ifdef _MS_WINDOWS + virtual void format(std::basic_string& str) const + { + format_impl(str); + } +#endif + private: + json::array m_array; + + template friend class json::details::JSON_Parser; + + template + void format_impl(std::basic_string& str) const + { + str.push_back('['); + if(!m_array.m_elements.empty()) + { + auto lastElement = m_array.m_elements.end() - 1; + for (auto iter = m_array.m_elements.begin(); iter != lastElement; ++iter) + { + iter->format(str); + str.push_back(','); + } + lastElement->format(str); + } + str.push_back(']'); + } + + size_t get_reserve_size() const + { + // This is a heuristic we can tune more in the future: + // Basically sum size of each value if an object, array, or string by a multiplier. + size_t reserveSize = 2; // For brackets [] + for(auto iter = m_array.cbegin(); iter != m_array.cend(); ++iter) + { + size_t valueSize = iter->size() * 20; // Per each nested array/object + + if(valueSize == 0) + valueSize = 5; // true, false, or null + + reserveSize += valueSize; + } + return reserveSize; + } + }; + } // namespace details + + /// + /// Gets the number of children of the value. + /// + /// The number of children. 0 for all non-composites. + inline size_t json::value::size() const + { + return m_value->size(); + } + + /// + /// Test for the presence of a field. + /// + /// The name of the field + /// True if the field exists, false otherwise. + inline bool json::value::has_field(const utility::string_t& key) const + { + return m_value->has_field(key); + } + + /// + /// Access a field of a JSON object. + /// + /// The name of the field + /// The value kept in the field; null if the field does not exist + inline json::value json::value::get(const utility::string_t& key) const + { + return m_value->get_field(key); + } + + /// + /// Access an element of a JSON array. + /// + /// The index of an element in the JSON array + /// The value kept at the array index; null if outside the boundaries of the array + inline json::value json::value::get(size_t index) const + { + return m_value->get_element(index); + } + + /// + /// A standard std::ostream operator to facilitate writing JSON values to streams. + /// + /// The output stream to write the JSON value to. + /// The JSON value to be written to the stream. + /// The output stream object + ODATACPP_API utility::ostream_t& operator << (utility::ostream_t &os, const json::value &val); + + /// + /// A standard std::istream operator to facilitate reading JSON values from streams. + /// + /// The input stream to read the JSON value from. + /// The JSON value object read from the stream. + /// The input stream object. + ODATACPP_API utility::istream_t& operator >> (utility::istream_t &is, json::value &val); + +}}} // namespace odata::utility::json + +#endif /* _ODATA_JSON_H */ diff --git a/include/odata/common/nullable.h b/include/odata/common/nullable.h new file mode 100644 index 0000000..5d125f3 --- /dev/null +++ b/include/odata/common/nullable.h @@ -0,0 +1,112 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include + +namespace odata { namespace common +{ + +#ifndef null_value +#ifdef WIN32 +#define null_value nullptr +#else +#define null_value ((std::nullptr_t)nullptr) +#endif +#endif + +template +class nullable +{ +public: + static_assert(std::is_pod::value, "nullable requires T to be POD type"); + + nullable() + : m_has_value(false), m_value() {} + + nullable(std::nullptr_t) + : m_has_value(false), m_value() {} + + nullable(T y) + : m_has_value(true), m_value(y) {} + + nullable(const nullable &y) + : m_has_value(y.has_value()), m_value(y.value()) {} + + ~nullable() {} + + bool has_value() const { return m_has_value; } + + T value() const { return m_value; } + + bool operator==(const nullable &y) const + { + return (!has_value() && !y.has_value()) + || (has_value() && y.has_value() && value() == y.value()); + } + + template + bool operator==(const nullable &y) const + { return !has_value() && !y.has_value(); } + + bool operator==(std::nullptr_t) const + { return !has_value(); } + + bool operator==(T y) const + { return has_value() && value() == y; } + + template + bool operator!=(const nullable &y) const + { return !(*this == y); } + + bool operator!=(std::nullptr_t) const + { return !(*this == null_value); } + + bool operator!=(T y) const + { return !(*this == y); } + + nullable &operator=(const nullable &y) + { + m_has_value = y.has_value(); + m_value = y.value(); + + return *this; + } + + nullable &operator=(std::nullptr_t) + { + m_has_value = false; + + return *this; + } + + nullable &operator=(T y) + { + m_has_value = true; + m_value = y; + + return *this; + } + +private: + bool m_has_value; + T m_value; +}; + +template +bool operator==(T x, const nullable &y) { return y == x; } + +template +bool operator!=(T x, const nullable &y) { return y != x; } + +template +bool operator==(std::nullptr_t, const nullable &y) { return y == null_value; } + +template +bool operator!=(std::nullptr_t, const nullable &y) { return y != null_value; } + +}} \ No newline at end of file diff --git a/include/odata/common/platform.h b/include/odata/common/platform.h new file mode 100644 index 0000000..cd3f0b1 --- /dev/null +++ b/include/odata/common/platform.h @@ -0,0 +1,88 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#ifndef _MS_WINDOWS +#if defined(_WIN32) || defined(__cplusplus_winrt) +#define _MS_WINDOWS +#endif +#endif // _MS_WINDOWS + +#ifdef _MSC_VER +#pragma warning(disable:4146 4521 4522 4566 4996) +#endif + +#ifndef ODATACPP_API +#ifdef ODATALIB_EXPORTS +#define ODATACPP_API __declspec(dllexport) +#else +#define ODATACPP_API __declspec(dllimport) +#endif +#endif + +// for guids, used in comm.h +#ifdef MS_TARGET_APPLE +#include "odata/common/compat/apple_compat.h" +#else +#ifdef _MS_WINDOWS +#include +#include "odata/common/compat/windows_compat.h" +#else +#include "boost/uuid/uuid.hpp" +#endif +#endif + +#define UNREACHABLE __assume(0) + +#ifdef _MS_WINDOWS +#include "odata/common/compat/windows_compat.h" +// use the debug version of the CRT if _DEBUG is defined +#ifdef _DEBUG + #define _CRTDBG_MAP_ALLOC + #include + #include +#endif + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +// Windows Header Files: +#define NOMINMAX +#endif + +#include +#include + +// Windows Header Files: +#if !defined(__cplusplus_winrt) +#include + +#endif // #if !defined(__cplusplus_winrt) +#else // LINUX or APPLE +#ifdef __APPLE__ +#include +#else +#include +#define FAILED(x) ((x) != 0) +#endif +#define __STDC_LIMIT_MACROS +#include +#include +#include +#include +#include +#include +#include +#include "pthread.h" +#include "boost/locale.hpp" +#include "boost/thread/condition_variable.hpp" +#include "boost/date_time/posix_time/posix_time_types.hpp" +#include "boost/bind/bind.hpp" +#include +#include +#include +#include +#endif // _MS_WINDOWS diff --git a/include/odata/common/uri.h b/include/odata/common/uri.h new file mode 100644 index 0000000..fe770b4 --- /dev/null +++ b/include/odata/common/uri.h @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#ifndef _ODATA_URI_H +#define _ODATA_URI_H + +#include "odata/common/base_uri.h" +#include "odata/common/uri_builder.h" + +#endif /* _ODATA_URI_H */ + + diff --git a/include/odata/common/uri_builder.h b/include/odata/common/uri_builder.h new file mode 100644 index 0000000..fa5d259 --- /dev/null +++ b/include/odata/common/uri_builder.h @@ -0,0 +1,264 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +#include "odata/common/base_uri.h" +#include "odata/common/xxpublic.h" + +namespace odata { namespace utility +{ + /// + /// Builder for constructing URIs incrementally. + /// + class uri_builder + { + public: + +#pragma region Constructors + /// + /// Creates a builder with an initially empty URI. + /// + uri_builder() {} + + /// + /// Creates a builder with a existing URI object. + /// + /// Encoded string containing the URI. + uri_builder(const uri &uri_str): m_uri(uri_str.m_components) {} + +#pragma endregion + +#pragma region Accessors + + /// + /// Get the scheme component of the URI as an encoded string. + /// + /// The URI scheme as a string. + const utility::string_t &scheme() const { return m_uri.m_scheme; } + + /// + /// Get the user information component of the URI as an encoded string. + /// + /// The URI user information as a string. + const utility::string_t &user_info() const { return m_uri.m_user_info; } + + /// + /// Get the host component of the URI as an encoded string. + /// + /// The URI host as a string. + const utility::string_t &host() const { return m_uri.m_host; } + + /// + /// Get the port component of the URI. Returns -1 if no port is specified. + /// + /// The URI port as an integer. + int port() const { return m_uri.m_port; } + + /// + /// Get the path component of the URI as an encoded string. + /// + /// The URI path as a string. + const utility::string_t &path() const { return m_uri.m_path; } + + /// + /// Get the query component of the URI as an encoded string. + /// + /// The URI query as a string. + const utility::string_t &query() const { return m_uri.m_query; } + + /// + /// Get the fragment component of the URI as an encoded string. + /// + /// The URI fragment as a string. + const utility::string_t &fragment() const { return m_uri.m_fragment; } + +#pragma endregion + +#pragma region Modifiers + + /// + /// Set the scheme of the URI. + /// + /// Uri scheme. + /// A reference to this uri_builder to support chaining. + uri_builder & set_scheme(const utility::string_t &scheme) + { + m_uri.m_scheme = scheme; + return *this; + } + + /// + /// Set the user info component of the URI. + /// + /// User info as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder & set_user_info(const utility::string_t &user_info, bool do_encoding = false) + { + m_uri.m_user_info = do_encoding ? uri::encode_uri(user_info, uri::components::user_info) : user_info; + return *this; + } + + /// + /// Set the host component of the URI. + /// + /// Host as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder & set_host(const utility::string_t &host, bool do_encoding = false) + { + m_uri.m_host = do_encoding ? uri::encode_uri(host, uri::components::host) : host; + return *this; + } + + /// + /// Set the port component of the URI. + /// + /// Port as an integer. + /// A reference to this uri_builder to support chaining. + uri_builder & set_port(int port) + { + m_uri.m_port = port; + return *this; + } + + /// + /// Set the port component of the URI. + /// + /// Port as a string. + /// A reference to this uri_builder to support chaining. + /// When string can't be converted to an integer the port is left unchanged. + uri_builder & set_port(const utility::string_t &port) + { + utility::istringstream_t portStream(port); + int port_tmp; + portStream >> port_tmp; + if(portStream.fail() || portStream.bad()) + { + throw std::invalid_argument("invalid port argument, must be non empty string containing integer value"); + } + m_uri.m_port = port_tmp; + return *this; + } + + /// + /// Set the path component of the URI. + /// + /// Path as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder & set_path(const utility::string_t &path, bool do_encoding = false) + { + m_uri.m_path = do_encoding ? uri::encode_uri(path, uri::components::path) : path; + return *this; + } + + + /// + /// Set the query component of the URI. + /// + /// Query as a decoded string. + /// Specify whether apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder & set_query(const utility::string_t &query, bool do_encoding = false) + { + m_uri.m_query = do_encoding ? uri::encode_uri(query, uri::components::query) : query; + return *this; + } + + /// + /// Set the fragment component of the URI. + /// + /// Fragment as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + uri_builder & set_fragment(const utility::string_t &fragment, bool do_encoding = false) + { + m_uri.m_fragment = do_encoding ? uri::encode_uri(fragment, uri::components::fragment) : fragment; + return *this; + } + + /// + /// Clears all components of the underlying URI in this uri_builder. + /// + void clear() + { + m_uri = details::_uri_components(); + } + +#pragma endregion + +#pragma region Appending + /// + /// Appends another path to the path of this uri_builder. + /// + /// Path to append as a already encoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + ODATACPP_API uri_builder &append_path(const utility::string_t &path, bool do_encoding = false); + + /// + /// Appends another query to the query of this uri_builder. + /// + /// Query to append as a decoded string. + /// Specify whether to apply URI encoding to the given string. + /// A reference to this uri_builder to support chaining. + ODATACPP_API uri_builder &append_query(const utility::string_t &query, bool do_encoding = false); + + /// + /// Appends an relative uri (Path, Query and fragment) at the end of the current uri. + /// + /// The relative uri to append. + /// A reference to this uri_builder to support chaining. + ODATACPP_API uri_builder &append(const uri &relative_uri); + + /// + /// Appends another query to the query of this uri_builder, encoding it first. This overload is useful when building a query segment of + /// the form "element=10", where the right hand side of the query is stored as a type other than a string, for instance, an integral type. + /// + /// The name portion of the query string + /// The value portion of the query string + /// A reference to this uri_builder to support chaining. + template + uri_builder &append_query(utility::string_t name, const T &value) + { + utility::ostringstream_t ss; + ss << name << _XPLATSTR("=") << value; + return append_query(ss.str(), true); + } + +#pragma endregion + +#pragma region URI Creation + /// + /// Combine and validate the URI components into a encoded string. An exception will be thrown if the URI is invalid. + /// + /// The created URI as a string. + ODATACPP_API utility::string_t to_string(); + + /// + /// Combine and validate the URI components into a URI class instance. An exception will be thrown if the URI is invalid. + /// + /// The create URI as a URI class instance. + ODATACPP_API uri to_uri(); + + /// + /// Validate the generated URI from all existing components of this uri_builder. + /// + /// Whether the URI is valid. + ODATACPP_API bool is_valid(); +#pragma endregion + + private: + details::_uri_components m_uri; + }; +} // namespace web +} diff --git a/include/odata/common/uri_parser.h b/include/odata/common/uri_parser.h new file mode 100644 index 0000000..b32992d --- /dev/null +++ b/include/odata/common/uri_parser.h @@ -0,0 +1,206 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include + +namespace odata { namespace utility { +namespace details +{ + class uri_parser + { + public: + uri_parser() {} + + /// + /// Parses the uri, attempting to determine its validity. + /// + /// This function accepts both uris ('http://msn.com') and uri relative-references ('path1/path2?query') + /// + bool validate(const utility::string_t &encoded_string) const; + + /// + /// Parses the uri, setting each provided string to the value of that component. Components + /// that are not part of the provided text are set to the empty string. Component strings + /// DO NOT contain their beginning or ending delimiters. + /// + /// This function accepts both uris ('http://msn.com') and uri relative-references ('path1/path2?query') + /// + bool parse( + const utility::string_t &encoded_string, + _uri_components &components) const; + + /// + /// Unreserved characters are those that are allowed in a URI but do not have a reserved purpose. They include: + /// - A-Z + /// - a-z + /// - 0-9 + /// - '-' (hyphen) + /// - '.' (period) + /// - '_' (underscore) + /// - '~' (tilde) + /// + static bool is_unreserved(int c) + { + return ::odata::utility::details::is_alnum((char)c) || c == '-' || c == '.' || c == '_' || c == '~'; + } + + /// + /// Reserved characters includes the general delimiters and sub delimiters. Some characters + /// are neither reserved nor unreserved, and must be percent-encoded. + /// + static bool is_reserved(int c) + { + return is_gen_delim(c) || is_sub_delim(c); + } + + /// + /// General delimiters serve as the delimiters between different uri components. + /// General delimiters include: + /// - All of these :/?#[]@ + /// + static bool is_gen_delim(int c) + { + return c == ':' || c == '/' || c == '?' || c == '#' || c == '[' || c == ']' || c == '@'; + } + + /// + /// Subdelimiters are those characters that may have a defined meaning within component + /// of a uri for a particular scheme. They do not serve as delimiters in any case between + /// uri segments. sub_delimiters include: + /// - All of these !$&'()*+,;= + /// + static bool is_sub_delim(int c) + { + switch (c) + { + case '!': + case '$': + case '&': + case '\'': + case '(': + case ')': + case '*': + case '+': + case ',': + case ';': + case '=': + return true; + default: + return false; + } + } + + /// + /// Legal characters in the scheme portion include: + /// - Any alphanumeric character + /// - '+' (plus) + /// - '-' (hyphen) + /// - '.' (period) + /// + /// Note that the scheme must BEGIN with an alpha character. + /// + static bool is_scheme_character(int c) + { + return ::odata::utility::details::is_alnum((char)c) || c == '+' || c == '-' || c == '.'; + } + + /// + /// Legal characters in the user information portion include: + /// - Any unreserved character + /// - The percent character ('%'), and thus any percent-endcoded octet + /// - The sub-delimiters + /// - ':' (colon) + /// + static bool is_user_info_character(int c) + { + return is_unreserved(c) || is_sub_delim(c) || c == '%' || c == ':'; + } + + /// + /// Legal characters in the host portion include: + /// - Any unreserved character + /// - The percent character ('%'), and thus any percent-endcoded octet + /// - The sub-delimiters + /// - ':' (colon) + /// - '[' (open bracket) + /// - ']' (close bracket) + /// + static bool is_host_character(int c) + { + return is_unreserved(c) || is_sub_delim(c) || c == '%' || c == ':' || c == '[' || c == ']'; + } + + /// + /// Legal characters in the authority portion include: + /// - Any unreserved character + /// - The percent character ('%'), and thus any percent-endcoded octet + /// - The sub-delimiters + /// - ':' (colon) + /// + /// Note that we don't currently support: + /// - IPv6 addresses (requires '[]') + /// + static bool is_authority_character(int c) + { + return is_unreserved(c) || is_sub_delim(c) || c == '%' || c == '@' || c == ':'; + } + + /// + /// Legal characters in the path portion include: + /// - Any unreserved character + /// - The percent character ('%'), and thus any percent-endcoded octet + /// - The sub-delimiters + /// - ':' (colon) + /// - '@' (ampersand) + /// + static bool is_path_character(int c) + { + return is_unreserved(c) || is_sub_delim(c) || c == '%' || c == '/' || c == ':' || c == '@'; + } + + /// + /// Legal characters in the query portion include: + /// - Any path character + /// - '?' (question mark) + /// + static bool is_query_character(int c) + { + return is_path_character(c) || c == '?'; + } + + /// + /// Legal characters in the fragment portion include: + /// - Any path character + /// - '?' (question mark) + /// + static bool is_fragment_character(int c) + { + // this is intentional, they have the same set of legal characters + return is_query_character(c); + } + + private: + /// + /// Parses the uri, setting the given pointers to locations inside the given buffer. + /// 'encoded' is expected to point to an encoded zero-terminated string containing a uri + /// + bool inner_parse( + const utility::char_t *encoded, + const utility::char_t **scheme_begin, const utility::char_t **scheme_end, + const utility::char_t **uinfo_begin, const utility::char_t **uinfo_end, + const utility::char_t **host_begin, const utility::char_t **host_end, + _Out_ int *port, + const utility::char_t **path_begin, const utility::char_t **path_end, + const utility::char_t **query_begin, const utility::char_t **query_end, + const utility::char_t **fragment_begin, const utility::char_t **fragment_end) const; + + static const std::locale loc; + }; +}} +} diff --git a/include/odata/common/utility.h b/include/odata/common/utility.h new file mode 100644 index 0000000..09abab9 --- /dev/null +++ b/include/odata/common/utility.h @@ -0,0 +1,76 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "odata/common/basic_types.h" +#include "odata/common/uri.h" +#include "odata/common/json.h" + +namespace odata { namespace utility +{ + +ODATACPP_API ::odata::utility::string_t strip_string(const ::odata::utility::string_t& escaped); + +ODATACPP_API void split_string(::odata::utility::string_t& source, const ::odata::utility::string_t& delim, std::list<::odata::utility::string_t>& ret); + +template +void to_string(const T& input, ::odata::utility::string_t& result) +{ + odata::utility::stringstream_t ostr; + ostr << input; + result = ostr.str(); +} + +template +bool bind(const key_type &text, _t &ref) // const +{ + utility::istringstream_t iss(text); + iss >> ref; + if (iss.fail() || !iss.eof()) + { + return false; + } + return true; +} + +template +bool bind(const key_type &text, utility::string_t &ref) //const +{ + ref = text; + return true; +} + +ODATACPP_API bool is_relative_path(const ::odata::utility::string_t& root_url, const ::odata::utility::string_t& path); + +ODATACPP_API ::odata::utility::string_t print_double(const double& db, int precision = 20); +ODATACPP_API ::odata::utility::string_t print_float(const float& db, int precision = 16); + +ODATACPP_API bool is_digit(::odata::utility::char_t c); +ODATACPP_API bool is_hex_digit(::odata::utility::char_t c); +ODATACPP_API bool is_letter(::odata::utility::char_t c); +ODATACPP_API bool is_letter_or_digit(::odata::utility::char_t c); + +}} diff --git a/include/odata/common/xmlhelpers.h b/include/odata/common/xmlhelpers.h new file mode 100644 index 0000000..a504a9d --- /dev/null +++ b/include/odata/common/xmlhelpers.h @@ -0,0 +1,281 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* 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. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* xmlhelpers.h +* +* This file contains xml parsing helper routines +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#pragma once +#ifndef _XMLHELPERS_H +#define _XMLHELPERS_H +#ifdef WIN32 +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include "odata/common/basic_types.h" + +namespace odata { namespace edm { + +/// +/// XML reader based on xmlllite in Windows and libxml2 in other platform. +/// +class xml_reader +{ +public: + + virtual ~xml_reader() {} + + /// + /// Parse the given xml string/stream. Returns true if it finished parsing the stream to the end, and false + /// if it was asked to exit early via pause() + /// + bool parse(); + +protected: + + xml_reader() : m_continueParsing(true), m_streamDone(false) + { + } + + xml_reader(std::istream& stream) : m_continueParsing(true), m_streamDone(false) + { + initialize(stream); + } + + /// + /// Callback for handling the start of an element. + /// + virtual void handle_begin_element(const ::odata::utility::string_t&) + { + } + + /// + /// Callback for handling the element text. + /// + virtual void handle_element(const ::odata::utility::string_t& ) + { + } + + /// + /// Callback for handling the end of an element. + /// + virtual void handle_end_element(const ::odata::utility::string_t& ) + { + } + + /// + /// Logs an error from processing XML + /// + virtual void log_error_message(const std::string& message, unsigned long error = 0) + { + UNREFERENCED_PARAMETER(message); + UNREFERENCED_PARAMETER(error); + } + + /// + /// Returns the parent element name + /// + ::odata::utility::string_t get_parent_element_name(size_t pos = 0); + + /// + /// Returns the current element name + /// + ::odata::utility::string_t get_current_element_name(); + + /// + /// Returns the current element name with the prefix if any. + /// + ::odata::utility::string_t get_current_element_name_with_prefix(); + + /// + /// Returns the current element value + /// + ::odata::utility::string_t get_current_element_text(); + + /// + /// Moves to the first attribute in the node + /// + bool move_to_first_attribute(); + + /// + /// Moves to the first attribute in the node + /// + bool move_to_next_attribute(); + + /// + /// Extracts the current element value into the provided type + /// + template + void extract_current_element(T& value) + { + ::odata::utility::istringstream_t iss(get_current_element_text()); + iss >> value; + } + + /// + /// Initialize the reader + /// + ODATACPP_API void initialize(std::istream& stream); + + /// + /// Remove Byte Order Mark from the stream + /// + void remove_bom(std::istream& stream); + + /// + /// Can be called by the derived classes in the handle_* routines, to cause the parse routine to exit early, + /// in order to capture records as they are parsed. Parsing is resumed by invoking the parse method again. + /// + void pause() { m_continueParsing = false; } + + /// + /// Read to the end of the input stream and save the text to a string. + /// + ::odata::utility::string_t read_to_end(std::istream& stream); + +#ifdef WIN32 + CComPtr m_reader; +#else + xmlTextReaderPtr m_reader; + std::string m_data; +#endif + + std::vector<::odata::utility::string_t> m_elementStack; + bool m_continueParsing; + bool m_streamDone; +}; + +/// +/// XML writer based on xmlllite in Windows and libxml2 in non-Windows +/// +class xml_writer +{ +public: + + virtual ~xml_writer() {} + +protected: + xml_writer() + { + } + + /// + /// Initialize the writer + /// + ODATACPP_API void initialize(std::ostream& stream); + + /// + /// Finalize the writer + /// + void finalize(); + + /// + /// Write the start element tag + /// + void write_start_element(const ::odata::utility::string_t& elementName, const ::odata::utility::string_t& namespaceName = U("")); + + /// + /// Writes the start element tag with a prefix + /// + void write_start_element_with_prefix(const ::odata::utility::string_t& elementPrefix, const ::odata::utility::string_t& elementName, + const ::odata::utility::string_t& namespaceName = U("")); + + /// + /// Write the end element tag for the current element + /// + void write_end_element(); + + /// + /// Write the full end element tag for the current element + /// + void write_full_end_element(); + + /// + /// Write an element including the name and text. + /// + template + void write_element(const ::odata::utility::string_t& elementName, T value) + { + write_element(elementName, convert_to_string(value)); + } + + /// + /// Write an element including the name and text. + /// + void write_element(const ::odata::utility::string_t& elementName, const ::odata::utility::string_t& value); + + /// + /// Write an element including the prefix, name and text. + /// + void write_element_with_prefix(const ::odata::utility::string_t& prefix, const ::odata::utility::string_t& elementName, const ::odata::utility::string_t& value); + + /// + /// Write raw data + /// + void write_raw(const ::odata::utility::string_t& data); + + /// + /// Write a string + /// + void write_string(const ::odata::utility::string_t& string); + + /// + /// Write an attribute string with a prefix + /// + void write_attribute_string(const ::odata::utility::string_t& prefix, const ::odata::utility::string_t& name, + const ::odata::utility::string_t& namespaceUri, const ::odata::utility::string_t& value); + + + /// + /// Logs an error from processing XML + /// + virtual void log_error_message(const std::string& message, unsigned long error = 0) + { + UNREFERENCED_PARAMETER(message); + UNREFERENCED_PARAMETER(error); + } +private: +#ifdef WIN32 + CComPtr m_writer; +#else // LINUX + xmlTextWriterPtr m_writer; + xmlDocPtr m_doc; + std::ostream * m_stream; +#endif +}; + +}} // namespace odata::edm + +#endif diff --git a/include/odata/common/xmlstream.h b/include/odata/common/xmlstream.h new file mode 100644 index 0000000..514514c --- /dev/null +++ b/include/odata/common/xmlstream.h @@ -0,0 +1,224 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* 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. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* xmlstream.h +* +* This file contains xml stream implementation +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#pragma once + +#include +#include +#include + +namespace odata { namespace edm { + +/// +/// A base implementation for IStream that returns E_NOTIMPL for all the methods +/// +class xmlstring_stream : public IStream +{ +protected: + xmlstring_stream() + : m_refCount(1) + { + } + + virtual ~xmlstring_stream() + { + } + +public: + + virtual HRESULT STDMETHODCALLTYPE QueryInterface(REFIID iid, void ** ppvObject) + { + if (iid == __uuidof(IUnknown) + || iid == __uuidof(IStream) + || iid == __uuidof(ISequentialStream)) + { + *ppvObject = static_cast(this); + AddRef(); + return S_OK; + } else + return E_NOINTERFACE; + } + + virtual ULONG STDMETHODCALLTYPE AddRef(void) + { + return (ULONG)_InterlockedIncrement(&m_refCount); + } + + virtual ULONG STDMETHODCALLTYPE Release(void) + { + ULONG res = (ULONG) _InterlockedDecrement(&m_refCount); + if (res == 0) + { + delete this; + } + return res; + } + + // ISequentialStream Interface +public: + + virtual HRESULT STDMETHODCALLTYPE Read(void* , ULONG , ULONG* ) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Write(void const* , ULONG , ULONG* ) + { + return E_NOTIMPL; + } + + // IStream Interface +public: + virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE CopyTo(IStream*, ULARGE_INTEGER, ULARGE_INTEGER*, + ULARGE_INTEGER*) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Commit(DWORD) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Revert(void) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE LockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE UnlockRegion(ULARGE_INTEGER, ULARGE_INTEGER, DWORD) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Clone(IStream **) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER , DWORD , ULARGE_INTEGER* ) + { + return E_NOTIMPL; + } + + virtual HRESULT STDMETHODCALLTYPE Stat(STATSTG* , DWORD ) + { + return E_NOTIMPL; + } + +private: + + volatile long m_refCount; +}; + +/// +/// An IStream implementation for reading xmlstrings +/// +class xmlstring_istream : public xmlstring_stream +{ +public: + + static xmlstring_istream* create(std::istream& stream) + { + return new xmlstring_istream(stream); + } + + virtual HRESULT STDMETHODCALLTYPE Read(_Out_writes_ (cb) void* pv, _In_ ULONG cb, ULONG* pcbRead ) + { + if (cb > 0) + { + // Synchronous for now. + m_stream.read((char *)pv, static_cast(cb)); + *pcbRead = (ULONG)m_stream.gcount(); + return S_OK; + } + + *pcbRead = cb; + return S_OK; + } + +protected: + + xmlstring_istream(std::istream& stream) + : m_stream(stream) + { + } + + std::istream& m_stream; +}; + +/// +/// An IStream implementation for writing xml strings +/// +class xmlstring_ostream : public xmlstring_stream +{ +public: + + static xmlstring_ostream* create(std::ostream& stream) + { + return new xmlstring_ostream(stream); + } + + virtual HRESULT STDMETHODCALLTYPE Write(void const* pv, ULONG cb, ULONG* pcbWritten) + { + // This method gets called when the XmlWriter is destroyed with cb == 0. We need this + // check to ensure that we do not access the underlying stream after finalize is called. + if (cb > 0) + { + const char * buf = (const char *) pv; + std::string s(buf, cb); + m_stream << s; + } + + *pcbWritten = cb; + return S_OK; + } + +protected: + + xmlstring_ostream(std::ostream& stream) + : m_stream(stream) + { + } + + std::ostream& m_stream; +}; + +}} // namespace odata::edm diff --git a/include/odata/common/xxpublic.h b/include/odata/common/xxpublic.h new file mode 100644 index 0000000..adcef3e --- /dev/null +++ b/include/odata/common/xxpublic.h @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#ifndef _MS_WINDOWS +#if defined(_WIN32) || defined(__cplusplus_winrt) +#define _MS_WINDOWS +#endif +#endif // _MS_WINDOWS + + +// for guids, used in comm.h +#ifdef MS_TARGET_APPLE +#include "odata/common/compat/apple_compat.h" +#else +#ifdef _MS_WINDOWS +#include +#include "odata/common/compat/windows_compat.h" +#else +#include "boost/uuid/uuid.hpp" +#endif +#endif + +#define UNREACHABLE __assume(0) \ No newline at end of file diff --git a/include/odata/core/odata_batch_part_value.h b/include/odata/core/odata_batch_part_value.h new file mode 100644 index 0000000..34c4b1c --- /dev/null +++ b/include/odata/core/odata_batch_part_value.h @@ -0,0 +1,72 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_value.h" + +namespace odata { namespace core +{ +class odata_batch_part_value +{ +public: + odata_batch_part_value():m_status_code(200), m_status_message(U("OK")) + { + } + + std::shared_ptr<::odata::core::odata_value> get_odata_value() + { + return m_value; + } + + void set_odata_value(std::shared_ptr<::odata::core::odata_value> odata_value) + { + m_value = odata_value; + } + + int16_t get_status_code() + { + return m_status_code; + } + + void set_status_code(int16_t status_code) + { + m_status_code = status_code; + } + + ::odata::utility::string_t get_status_message() + { + return m_status_message; + } + + void set_status_message(::odata::utility::string_t status_message) + { + m_status_message = status_message; + } + + void set_header(::odata::utility::string_t name, ::odata::utility::string_t value) + { + m_headers[name] = value; + } + + ::odata::utility::string_t get_header(::odata::utility::string_t name) + { + return m_headers[name]; + } + + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> get_headers() + { + return m_headers; + } + +private: + std::shared_ptr<::odata::core::odata_value> m_value; + int16_t m_status_code; + ::odata::utility::string_t m_status_message; + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> m_headers; +}; +}} \ No newline at end of file diff --git a/include/odata/core/odata_batch_value.h b/include/odata/core/odata_batch_value.h new file mode 100644 index 0000000..a985c2b --- /dev/null +++ b/include/odata/core/odata_batch_value.h @@ -0,0 +1,46 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_batch_part_value.h" + +namespace odata { namespace core +{ +class odata_batch_value +{ + public: + ::odata::utility::string_t get_boundary() + { + return m_boundary; + } + + void set_boundary(::odata::utility::string_t boundary) + { + m_boundary = boundary; + } + + std::vector>::const_iterator cbegin() const + { + return m_parts.cbegin(); + } + + std::vector>::const_iterator cend() const + { + return m_parts.cend(); + } + + void add_part(std::shared_ptr batch_part_value) + { + m_parts.push_back(batch_part_value); + } + + private: + ::odata::utility::string_t m_boundary; + std::vector> m_parts; +}; +}} \ No newline at end of file diff --git a/include/odata/core/odata_collection_value.h b/include/odata/core/odata_collection_value.h new file mode 100644 index 0000000..623965f --- /dev/null +++ b/include/odata/core/odata_collection_value.h @@ -0,0 +1,81 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_value.h" +#include "odata/common/nullable.h" + +namespace odata { namespace core +{ + +class odata_collection_value : public odata_value +{ +public: + odata_collection_value(std::shared_ptr<::odata::edm::edm_named_type> type) : odata_value(type) + { + } + + void add_collection_value(std::shared_ptr value) + { + m_values.push_back(value); + } + + void add_collection_value(const std::vector>& values) + { + for (auto iter = values.cbegin(); iter != values.cend(); iter++) + { + m_values.push_back(*iter); + } + } + + const std::vector>& get_collection_values() const + { + return m_values; + } + + void set_next_link(::odata::utility::uri value) + { + m_next_link = value; + } + + const ::odata::utility::uri get_next_link() const + { + return m_next_link; + } + + void set_delta_link(::odata::utility::uri value) + { + m_delta_link = value; + } + + const ::odata::utility::uri get_delta_link() const + { + return m_delta_link; + } + + void set_count(int64_t count) + { + m_count = count; + } + + const ::odata::common::nullable get_count() const + { + return m_count; + } + +private: + std::vector> m_values; + + ::odata::utility::uri m_next_link; + ::odata::utility::uri m_delta_link; + + ::odata::common::nullable m_count; + +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_complex_value.h b/include/odata/core/odata_complex_value.h new file mode 100644 index 0000000..cc91c88 --- /dev/null +++ b/include/odata/core/odata_complex_value.h @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_value.h" +#include "odata/core/odata_structured_value.h" + +namespace odata { namespace core +{ + +class odata_complex_value : public odata_structured_value +{ +public: + odata_complex_value(std::shared_ptr<::odata::edm::edm_named_type> type) : odata_structured_value(type) + { + } +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_context_url_builder.h b/include/odata/core/odata_context_url_builder.h new file mode 100644 index 0000000..8b12508 --- /dev/null +++ b/include/odata/core/odata_context_url_builder.h @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_writer.h" +#include "odata/core/odata_value.h" +#include "odata/core/odata_json_writer.h" +#include "odata/core/odata_service_document.h" +#include "odata/common/json.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ +class odata_context_url_builder +{ +public: + odata_context_url_builder(std::shared_ptr<::odata::edm::edm_model> model, ::odata::utility::uri service_root) + : m_model(model) + { + m_builder = ::odata::utility::uri_builder(service_root); + m_metadata_url = m_builder.append_path(U("$metadata")).to_uri(); + } + + ::odata::utility::uri get_context_uri_for_service_document(std::shared_ptr<::odata::core::odata_service_document> service_document) + { + return m_metadata_url; + } + + ::odata::utility::uri get_context_uri_for_collection_of_entities(std::shared_ptr<::odata::edm::edm_entity_set> entity_set) + { + ::odata::utility::uri_builder builder(m_metadata_url); + return builder.append_path(entity_set->get_name()).to_uri(); + } + + ::odata::utility::uri get_context_uri_for_entity(std::shared_ptr<::odata::edm::edm_entity_set> entity_set) + { + ::odata::utility::uri_builder builder(m_metadata_url); + return builder.append_path(entity_set->get_name()).append_path(U("$entity")).to_uri(); + } + + ::odata::utility::uri get_context_uri_for_singleton(std::shared_ptr<::odata::edm::edm_singleton> singleton) + { + ::odata::utility::uri_builder builder(m_metadata_url); + return builder.append_path(singleton->get_name()).to_uri(); + } + + ::odata::utility::uri get_context_uri_for_collection_of_dervied_entities(std::shared_ptr<::odata::edm::edm_entity_set> entity_set, std::shared_ptr<::odata::edm::edm_entity_type> entity_type) + { + ::odata::utility::uri_builder builder(m_metadata_url); + return builder.append_path(entity_set->get_name()).append_path(entity_type->get_full_name()).to_uri(); + } + + ::odata::utility::uri get_context_uri_for_derived_entity(std::shared_ptr<::odata::edm::edm_entity_set> entity_set, std::shared_ptr<::odata::edm::edm_entity_type> entity_type) + { + ::odata::utility::uri_builder builder(m_metadata_url); + return builder.append_path(entity_set->get_name()).append_path(entity_type->get_full_name()).append_path(U("$entity")).to_uri(); + } + +private: + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::uri m_metadata_url; + ::odata::utility::uri_builder m_builder; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_context_url_parser.h b/include/odata/core/odata_context_url_parser.h new file mode 100644 index 0000000..ae45e40 --- /dev/null +++ b/include/odata/core/odata_context_url_parser.h @@ -0,0 +1,36 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_core.h" +#include "odata/common/json.h" +#include "odata/edm/odata_edm.h" + +namespace odata { namespace core +{ + +class odata_contex_url_parser +{ +public: + odata_contex_url_parser(std::shared_ptr<::odata::edm::edm_model> model, const ::odata::utility::string_t& service_root_url) + : m_model(model), m_service_root_url(service_root_url) + { + } + + ODATACPP_API std::shared_ptr<::odata::edm::edm_named_type> get_payload_content_type(const ::odata::utility::string_t& context_url); + +private: + std::shared_ptr<::odata::edm::edm_named_type> parse_complex_or_primitive(const ::odata::utility::string_t& current_path); + bool end_with(const ::odata::utility::string_t& s1, const ::odata::utility::string_t& s2); + std::shared_ptr<::odata::edm::edm_named_type> resolve_literal_type(const ::odata::utility::string_t& type_name); + + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::string_t m_service_root_url; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_core.h b/include/odata/core/odata_core.h new file mode 100644 index 0000000..1591d6d --- /dev/null +++ b/include/odata/core/odata_core.h @@ -0,0 +1,18 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/core/odata_value.h" +#include "odata/core/odata_collection_value.h" +#include "odata/core/odata_complex_value.h" +#include "odata/core/odata_primitive_value.h" +#include "odata/core/odata_enum_value.h" +#include "odata/core/odata_property_map.h" +#include "odata/core/odata_entity_value.h" +#include "odata/core/odata_entity_model_builder.h" +#include "odata/core/odata_parameter.h" +#include "odata/core/odata_exception.h" diff --git a/include/odata/core/odata_entity_factory.h b/include/odata/core/odata_entity_factory.h new file mode 100644 index 0000000..229c0e3 --- /dev/null +++ b/include/odata/core/odata_entity_factory.h @@ -0,0 +1,37 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_core.h" +#include "odata/edm/odata_edm.h" +#include "odata/common/json.h" + +namespace odata { namespace core +{ + +template +class entity_factory +{ +public: + static std::shared_ptr<_Entity_Impl> create_reader_instance(std::shared_ptr<::odata::edm::edm_model> model, const ::odata::utility::string_t& service_root_url) + { + return std::make_shared<_Entity_Impl>(model, service_root_url); + } + + static std::shared_ptr<_Entity_Impl> create_writer_instance(std::shared_ptr<::odata::edm::edm_model> model) + { + return std::make_shared<_Entity_Impl>(model); + } + + static std::shared_ptr<_Entity_Impl> create_context_url_parser(std::shared_ptr<::odata::edm::edm_model> model, const ::odata::utility::string_t& service_root_url) + { + return std::make_shared<_Entity_Impl>(model, service_root_url); + } +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_entity_model_builder.h b/include/odata/core/odata_entity_model_builder.h new file mode 100644 index 0000000..7595a2c --- /dev/null +++ b/include/odata/core/odata_entity_model_builder.h @@ -0,0 +1,27 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/core/odata_entity_value.h" +#include "odata/edm/odata_edm.h" + +namespace odata { namespace core +{ + +class odata_entity_model_builder +{ +public: + ODATACPP_API static ::odata::utility::string_t compute_edit_link( + const ::odata::utility::string_t& root_url, + std::shared_ptr entity_value, + const ::odata::utility::string_t& parent_edit_link, + bool is_collection_navigation); + + ODATACPP_API static ::odata::utility::string_t get_entity_key_value_string(const std::shared_ptr& entity_value); +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_entity_reference.h b/include/odata/core/odata_entity_reference.h new file mode 100644 index 0000000..916a21a --- /dev/null +++ b/include/odata/core/odata_entity_reference.h @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ + +class odata_entity_reference +{ +public: + odata_entity_reference(::odata::utility::uri id) : m_id(id) + { + } + + ::odata::utility::uri get_id() + { + return m_id; + } + + void set_id(::odata::utility::uri id) + { + m_id = id; + } + + void set_context_url(::odata::utility::uri value) + { + m_context_url = value; + } + + ::odata::utility::uri get_context_url() + { + return m_context_url; + } + +private: + ::odata::utility::uri m_context_url; + ::odata::utility::uri m_id; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_entity_reference_collection.h b/include/odata/core/odata_entity_reference_collection.h new file mode 100644 index 0000000..4eeda8f --- /dev/null +++ b/include/odata/core/odata_entity_reference_collection.h @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/common/nullable.h" +#include "odata/core/odata_entity_reference.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ + +class odata_entity_reference_collection +{ +public: + odata_entity_reference_collection() + { + + } + + void add_entity_refernece(std::shared_ptr reference) + { + m_references.push_back(reference); + } + + ::std::vector>::const_iterator cbegin() const + { + return m_references.cbegin(); + } + + ::std::vector>::const_iterator cend() const + { + return m_references.cend(); + } + + void set_context_url(::odata::utility::uri value) + { + m_context_url = value; + } + + ::odata::utility::uri get_context_url() + { + return m_context_url; + } + + void set_next_link(::odata::utility::uri value) + { + m_next_link = value; + } + + const ::odata::utility::uri get_next_link() const + { + return m_next_link; + } + + void set_delta_link(::odata::utility::uri value) + { + m_delta_link = value; + } + + const ::odata::utility::uri get_delta_link() const + { + return m_delta_link; + } + + void set_count(int64_t count) + { + m_count = count; + } + + const ::odata::common::nullable get_count() const + { + return m_count; + } + + int size() const + { + return m_references.size(); + } + +private: + ::std::vector> m_references; + ::odata::utility::uri m_context_url; + + ::odata::utility::uri m_next_link; + ::odata::utility::uri m_delta_link; + + ::odata::common::nullable m_count; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_entity_value.h b/include/odata/core/odata_entity_value.h new file mode 100644 index 0000000..b33a017 --- /dev/null +++ b/include/odata/core/odata_entity_value.h @@ -0,0 +1,75 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_value.h" +#include "odata/core/odata_structured_value.h" + +namespace odata { namespace core +{ + +class odata_entity_value : public odata_structured_value +{ +public: + odata_entity_value(std::shared_ptr<::odata::edm::edm_entity_type> type) : odata_structured_value(type) + {} + + odata_entity_value(odata_property_map properties, std::shared_ptr<::odata::edm::edm_entity_type> type) : odata_structured_value(type, properties) + {} + + ::odata::utility::string_t get_etag() + { + return m_etag; + } + + void set_etag(::odata::utility::string_t etag) + { + m_etag = etag; + } + + ::odata::utility::uri get_id() + { + return m_id; + } + + void set_id(::odata::utility::uri id) + { + m_id = id; + } + + ::odata::utility::uri get_edit_link() + { + return m_edit_link; + } + + void set_edit_link(::odata::utility::uri edit_link) + { + m_edit_link = edit_link; + } + + ::odata::utility::uri get_read_link() + { + return m_read_link; + } + + void set_read_link(::odata::utility::uri read_link) + { + m_read_link = read_link; + } + + ODATACPP_API ::odata::utility::string_t get_entity_key_string(); + +private: + ::odata::utility::string_t m_etag; + + ::odata::utility::uri m_id; + ::odata::utility::uri m_edit_link; + ::odata::utility::uri m_read_link; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_enum_value.h b/include/odata/core/odata_enum_value.h new file mode 100644 index 0000000..4eba9f2 --- /dev/null +++ b/include/odata/core/odata_enum_value.h @@ -0,0 +1,32 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_value.h" + +namespace odata { namespace core +{ + +class odata_enum_value : public odata_value +{ +public: + odata_enum_value(std::shared_ptr<::odata::edm::edm_named_type>type, const ::odata::utility::string_t& stringRep) : odata_value(type), m_string_rep(stringRep) + { + } + + const ::odata::utility::string_t& to_string() const + { + return m_string_rep; + } + +private: + ::odata::utility::string_t m_string_rep; + +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_error.h b/include/odata/core/odata_error.h new file mode 100644 index 0000000..783f9d7 --- /dev/null +++ b/include/odata/core/odata_error.h @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ + +class odata_error +{ +public: + odata_error(::odata::utility::string_t code, ::odata::utility::string_t message, ::odata::utility::string_t target) : m_code(code), m_message(message), m_target(target) + { + + } + + ::odata::utility::string_t get_code() + { + return m_message; + } + + ::odata::utility::string_t get_message() + { + return m_message; + } + + ::odata::utility::string_t get_target() + { + return m_target; + } + + +private: + ::odata::utility::string_t m_code; + ::odata::utility::string_t m_message; + ::odata::utility::string_t m_target; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_exception.h b/include/odata/core/odata_exception.h new file mode 100644 index 0000000..b4aedf8 --- /dev/null +++ b/include/odata/core/odata_exception.h @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/common/asyncrt_utils.h" + +namespace odata { namespace core +{ + +/// +/// Represents an OData exception +/// +class odata_exception : public std::exception +{ +public: + + /// + /// Constructor + /// + /// A string value containing the service error. + explicit odata_exception(::odata::utility::string_t error) : m_error(error) + { + } + + /// + /// Destructor + /// + ~odata_exception() _noexcept {} + + const ::odata::utility::string_t& what() + { + return m_error; + } + +private: + ::odata::utility::string_t m_error; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_filter_clause.h b/include/odata/core/odata_filter_clause.h new file mode 100644 index 0000000..dce3d9a --- /dev/null +++ b/include/odata/core/odata_filter_clause.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" + +namespace odata { namespace core +{ + +class odata_query_node; +class odata_range_variable; + +class odata_filter_clause +{ +public: + odata_filter_clause( + std::shared_ptr<::odata::core::odata_query_node> expression, + std::shared_ptr<::odata::core::odata_range_variable> range_variable) + : m_expression(expression), + m_range_variable(range_variable) {} + ~odata_filter_clause() {} + + std::shared_ptr<::odata::core::odata_query_node> expression() const { return m_expression; } + std::shared_ptr<::odata::core::odata_range_variable> range_variable() const { return m_range_variable; } + +private: + std::shared_ptr<::odata::core::odata_query_node> m_expression; + std::shared_ptr<::odata::core::odata_range_variable> m_range_variable; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_format.h b/include/odata/core/odata_format.h new file mode 100644 index 0000000..f74b48a --- /dev/null +++ b/include/odata/core/odata_format.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +namespace odata { namespace core +{ + +enum odata_format_type +{ + json_minimal_metadata, + json_full_metadata, + json_none_metadata, +}; + +class odata_format +{ +public: + odata_format(odata_format_type format_type) : m_format_type(format_type) + { + } + + odata_format_type get_format_type() + { + return m_format_type; + } + +private: + odata_format_type m_format_type; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_json_operation_payload_parameter_writer.h b/include/odata/core/odata_json_operation_payload_parameter_writer.h new file mode 100644 index 0000000..6f5efb9 --- /dev/null +++ b/include/odata/core/odata_json_operation_payload_parameter_writer.h @@ -0,0 +1,38 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/json.h" +#include "odata/common/utility.h" +#include "odata/core/odata_core.h" +#include "odata/edm/odata_edm.h" + +namespace odata { namespace core +{ + +class odata_json_operation_payload_parameter_writer +{ +public: + odata_json_operation_payload_parameter_writer(std::shared_ptr<::odata::edm::edm_model> model) : m_model(model) + { + } + + ODATACPP_API ::odata::utility::json::value serialize(std::vector> parameters); + +private: + void handle_serialize_odata_parameter(::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::core::odata_parameter>& parameter, ::odata::utility::char_t mark, ::odata::utility::char_t separator); + void handle_serialize_odata_value(::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_named_type>& property_type, const std::shared_ptr& property_value); + void handle_serialize_primitive_value(::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_primitive_type>& p_primitive_type, const std::shared_ptr& p_value); + void handle_serialize_enum_value(::odata::utility::stringstream_t& ss, const std::shared_ptr& p_value); + void handle_serialize_collection_value(::odata::utility::stringstream_t& ss, const std::shared_ptr& p_value); + void handle_serialize_odata_properties(::odata::utility::stringstream_t& ss, const odata_property_map& properties); + bool is_type_serializable(const std::shared_ptr<::odata::edm::edm_named_type>& property_type); + + std::shared_ptr<::odata::edm::edm_model> m_model; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_json_operation_url_parameter_writer.h b/include/odata/core/odata_json_operation_url_parameter_writer.h new file mode 100644 index 0000000..53e804d --- /dev/null +++ b/include/odata/core/odata_json_operation_url_parameter_writer.h @@ -0,0 +1,35 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/json.h" +#include "odata/common/utility.h" +#include "odata/core/odata_core.h" +#include "odata/edm/odata_edm.h" + +namespace odata { namespace core +{ + +class odata_json_operation_url_parameter_writer +{ +public: + odata_json_operation_url_parameter_writer(std::shared_ptr<::odata::edm::edm_model> model) : m_model(model) + { + } + + ODATACPP_API ::odata::utility::string_t serialize(std::vector> parameters); + +private: + void handle_serialize_odata_parameter(::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::core::odata_parameter>& parameter, ::odata::utility::char_t mark, ::odata::utility::char_t separator); + void handle_serialize_odata_value(::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_named_type>& property_type, const std::shared_ptr& property_value); + void handle_serialize_primitive_value(::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_primitive_type>& p_primitive_type, const std::shared_ptr& p_value); + void handle_serialize_enum_value(::odata::utility::stringstream_t& ss, const std::shared_ptr& p_value); + + std::shared_ptr<::odata::edm::edm_model> m_model; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_json_reader_full.h b/include/odata/core/odata_json_reader_full.h new file mode 100644 index 0000000..14fe47a --- /dev/null +++ b/include/odata/core/odata_json_reader_full.h @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/json.h" +#include "odata/common/utility.h" +#include "odata/core/odata_core.h" +#include "odata/edm/odata_edm.h" + +namespace odata { namespace core +{ + +class entity_json_reader_full +{ +public: + entity_json_reader_full(std::shared_ptr<::odata::edm::edm_model> model, const ::odata::utility::string_t& service_root_url) + : m_model(model), m_service_root_url(service_root_url) + { + }; + + ODATACPP_API std::shared_ptr deserilize(const ::odata::utility::json::value& response, std::shared_ptr<::odata::edm::edm_entity_set> set); + +private: + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::string_t m_service_root_url; + +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_json_reader_minimal.h b/include/odata/core/odata_json_reader_minimal.h new file mode 100644 index 0000000..7039aaa --- /dev/null +++ b/include/odata/core/odata_json_reader_minimal.h @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/json.h" +#include "odata/common/utility.h" +#include "odata/core/odata_core.h" +#include "odata/edm/odata_edm.h" + +namespace odata { namespace core +{ + +class odata_json_reader_minimal +{ +public: + odata_json_reader_minimal(std::shared_ptr<::odata::edm::edm_model> model, const ::odata::utility::string_t& service_root_url, bool is_reading_response = true) + : m_model(model), m_service_root_url(service_root_url), m_is_reading_response(is_reading_response) + { + }; + + ODATACPP_API std::shared_ptr deserilize(const odata::utility::json::value& content); + + ODATACPP_API std::shared_ptr deserilize_entity_value(const odata::utility::json::value& content, std::shared_ptr<::odata::edm::edm_entity_type> entity_type); + + ODATACPP_API std::shared_ptr deserilize_entity_collection(const odata::utility::json::value& content, std::shared_ptr<::odata::edm::edm_entity_set> entity_set); + + ODATACPP_API std::shared_ptr deserilize_property(const odata::utility::json::value& content, std::shared_ptr<::odata::edm::edm_named_type> edm_type); + +private: + std::shared_ptr handle_extract_entity_property(const odata::utility::json::value& value, std::shared_ptr<::odata::edm::edm_entity_type>& entity_type); + std::shared_ptr handle_extract_complex_property(const ::odata::utility::json::value& value, std::shared_ptr<::odata::edm::edm_complex_type>& edm_complex_type); + std::shared_ptr handle_extract_collection_property(std::shared_ptr<::odata::edm::edm_named_type> type, const odata::utility::json::value& value); + void handle_extract_entity_annotation(const ::odata::utility::string_t& annotation, const ::odata::utility::string_t& value, std::shared_ptr& entity_value); + std::shared_ptr handle_extract_navigation_property(const ::odata::utility::json::value& value, std::shared_ptr<::odata::edm::edm_navigation_type> navigation_type); + ::odata::utility::string_t get_navigation_source_from_context_url(const ::odata::utility::string_t& context_url); + void set_edit_link_for_entity_value(const std::shared_ptr& entity_value, const ::odata::utility::string_t& expect_type_name, const ::odata::utility::string_t& navigation_source); + void set_edit_link_for_entity_collection_value(const std::shared_ptr& entity_collection_value, const ::odata::utility::string_t& expect_type_name, const ::odata::utility::string_t& navigation_source); + std::shared_ptr<::odata::edm::edm_named_type> prepare_for_reading(const odata::utility::json::value& content, std::shared_ptr<::odata::edm::edm_named_type> edm_type); + + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::string_t m_service_root_url; + std::shared_ptr<::odata::edm::edm_entity_set> m_entity_set; + bool m_is_reading_response; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_json_writer.h b/include/odata/core/odata_json_writer.h new file mode 100644 index 0000000..5ff58d4 --- /dev/null +++ b/include/odata/core/odata_json_writer.h @@ -0,0 +1,77 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/json.h" +#include "odata/common/utility.h" +#include "odata/core/odata_core.h" +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_service_document.h" +#include "odata/core/odata_error.h" +#include "odata/core/odata_entity_reference.h" +#include "odata/core/odata_entity_reference_collection.h" + +namespace odata { namespace core +{ + +#define ANNOTATION_ODATA_CONTEXT U("@odata.context") +#define ANNOTATION_ODATA_COUNT U("@odata.count") +#define ANNOTATION_ODATA_ETAG U("@odata.etag") +#define ANNOTATION_ODATA_ID U("@odata.id") +#define ANNOTATION_ODATA_EDIT_LINK U("@odata.editLink") +#define ANNOTATION_ODATA_READ_LINK U("@odata.readLink") +#define ANNOTATION_ODATA_NEXT_LINK U("@odata.nextLink") +#define ANNOTATION_ODATA_DELTA_LINK U("@odata.deltaLink") +#define ANNOTATION_ODATA_TYPE U("@odata.type") +#define JSON_VALUE U("value") +#define JSON_NAME U("name") +#define JSON_KIND U("kind") +#define JSON_URI U("uri") + +#define JSON_ENTITYSET U("EntitySet"); +#define JSON_SINGLETON U("Singleton"); +#define JSON_FUNCTIONIMPORT U("FunctionImport"); +#define METADATA U("$metadata") + +class odata_json_writer +{ +public: + odata_json_writer(std::shared_ptr<::odata::edm::edm_model> model) : m_model(model) + { + } + + odata_json_writer(std::shared_ptr<::odata::edm::edm_model> model, ::odata::utility::uri service_root) + : m_model(model), m_service_root(service_root) + { + odata::utility::uri_builder builder(m_service_root); + builder.append_path(METADATA); + + m_metadata_document_uri = builder.to_uri(); + } + + ODATACPP_API ::odata::utility::json::value serialize(std::shared_ptr value_object); + ODATACPP_API ::odata::utility::json::value serialize(std::vector> parameters); + ODATACPP_API ::odata::utility::json::value serialize(std::shared_ptr service_document); + ODATACPP_API ::odata::utility::json::value serialize(std::shared_ptr error); + ODATACPP_API ::odata::utility::json::value serialize(std::shared_ptr entity_reference); + + ODATACPP_API ::odata::utility::json::value serialize(std::shared_ptr entity_reference_collection); + +private: + odata::utility::json::value serialize_odata_value(const std::shared_ptr<::odata::edm::edm_named_type>& property_type, const std::shared_ptr& property_value); + odata::utility::json::value seriliaze_structured_value(const std::shared_ptr& p_value); + odata::utility::json::value serialize_primitive_value(const std::shared_ptr<::odata::edm::edm_primitive_type>& p_primitive_type, const std::shared_ptr& p_value); + odata::utility::json::value serialize_enum_value(const std::shared_ptr& p_value); + odata::utility::json::value serialize_collection_value(const std::shared_ptr& p_value); + bool is_type_serializable(const std::shared_ptr<::odata::edm::edm_named_type>& property_type); + odata::utility::json::value serialize(std::shared_ptr service_document_element); + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::uri m_service_root; + ::odata::utility::uri m_metadata_document_uri; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_message_reader.h b/include/odata/core/odata_message_reader.h new file mode 100644 index 0000000..07f7b26 --- /dev/null +++ b/include/odata/core/odata_message_reader.h @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_value.h" +#include "odata/common/json.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ +class odata_message_reader +{ +public: + odata_message_reader(std::shared_ptr<::odata::edm::edm_model> model, ::odata::utility::uri base_uri, ::odata::utility::string_t message_body, bool is_response_message) + : m_model(model), m_base_uri(base_uri), m_message_body(message_body), m_is_response_message(is_response_message) + { + } + + ODATACPP_API std::shared_ptr<::odata::core::odata_value> read_odata_value(); + + ODATACPP_API std::shared_ptr<::odata::core::odata_entity_value> read_entity_value(std::shared_ptr<::odata::edm::edm_entity_type> edm_type); + ODATACPP_API std::shared_ptr<::odata::core::odata_entity_value> read_entity_value(); + + ODATACPP_API std::shared_ptr<::odata::core::odata_collection_value> read_entity_collection(std::shared_ptr<::odata::edm::edm_entity_set> entity_set); + ODATACPP_API std::shared_ptr<::odata::core::odata_collection_value> read_entity_collection(); + + ODATACPP_API std::shared_ptr<::odata::core::odata_value> read_property(std::shared_ptr<::odata::edm::edm_named_type> edm_type); + ODATACPP_API std::shared_ptr<::odata::core::odata_value> read_property(); + + //ODATACPP_API std::shared_ptr<::odata::core::odata_batch_value> read_batch_value(); + +private: + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::uri m_base_uri; + bool m_is_response_message; + ::odata::utility::string_t m_message_body; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_message_writer.h b/include/odata/core/odata_message_writer.h new file mode 100644 index 0000000..196e96d --- /dev/null +++ b/include/odata/core/odata_message_writer.h @@ -0,0 +1,55 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_writer.h" +#include "odata/core/odata_value.h" +#include "odata/core/odata_json_writer.h" +#include "odata/core/odata_service_document.h" +#include "odata/core/odata_batch_value.h" +#include "odata/core/odata_batch_part_value.h" +#include "odata/core/odata_entity_reference.h" +#include "odata/core/odata_entity_reference_collection.h" +#include "odata/core/odata_error.h" +#include "odata/common/json.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ + +class odata_message_writer +{ +public: + odata_message_writer(std::shared_ptr<::odata::edm::edm_model> model, ::odata::utility::uri service_root) + : m_model(model), m_service_root(service_root) + { + } + + ODATACPP_API ::odata::utility::string_t write_service_document(std::shared_ptr service_document); + + ODATACPP_API ::odata::utility::string_t write_metadata_document(); + + ODATACPP_API ::odata::utility::string_t write_odata_value(std::shared_ptr<::odata::core::odata_value> value); + + ODATACPP_API ::odata::utility::string_t write_asynchronous_odata_value(std::shared_ptr<::odata::core::odata_value> value, int16_t status_code, ::odata::utility::string_t status_message, std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> headers); + + ODATACPP_API ::odata::utility::string_t write_odata_batch_value(std::shared_ptr<::odata::core::odata_batch_value> batch_value); + + ODATACPP_API ::odata::utility::string_t write_odata_error(std::shared_ptr<::odata::core::odata_error> error); + + ODATACPP_API ::odata::utility::string_t write_odata_entity_reference(std::shared_ptr<::odata::core::odata_entity_reference> entity_reference); + + ODATACPP_API ::odata::utility::string_t write_odata_entity_reference_collection(std::shared_ptr<::odata::core::odata_entity_reference_collection> entity_reference_collection); + +private: + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::uri m_service_root; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_message_writer_settings.h b/include/odata/core/odata_message_writer_settings.h new file mode 100644 index 0000000..f00f422 --- /dev/null +++ b/include/odata/core/odata_message_writer_settings.h @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once +#include "odata/core/odata_format.h" + +namespace odata { namespace core +{ + +class odata_message_writer_settings +{ +public: + odata_message_writer_settings(odata_format format) : m_format(format) + { + } + + odata_message_writer_settings() : m_format(odata_format(odata_format_type::json_minimal_metadata)) + { + } + + odata_format get_format() + { + return m_format; + } + +private: + odata_format m_format; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_metadata_builder.h b/include/odata/core/odata_metadata_builder.h new file mode 100644 index 0000000..6c2d727 --- /dev/null +++ b/include/odata/core/odata_metadata_builder.h @@ -0,0 +1,57 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_writer.h" +#include "odata/core/odata_value.h" +#include "odata/core/odata_json_writer.h" +#include "odata/core/odata_service_document.h" +#include "odata/common/json.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ +class odata_metadata_builder +{ +public: + odata_metadata_builder(std::shared_ptr<::odata::edm::edm_model> model, ::odata::utility::uri service_root) + : m_model(model), m_service_root(service_root) + { + } + + ::odata::utility::uri get_entity_id(std::shared_ptr<::odata::core::odata_entity_value> entity_value, std::shared_ptr<::odata::edm::edm_entity_set> entity_set) + { + ::odata::utility::uri_builder builder(m_service_root); + return builder.append_path(entity_set->get_name() + entity_value->get_entity_key_string()).to_uri(); + } + + ::odata::utility::uri get_read_link(std::shared_ptr<::odata::core::odata_entity_value> entity_value, std::shared_ptr<::odata::edm::edm_entity_set> entity_set) + { + return get_edit_link(entity_value, entity_set); + } + + ::odata::utility::uri get_edit_link(std::shared_ptr<::odata::core::odata_entity_value> entity_value, std::shared_ptr<::odata::edm::edm_entity_set> entity_set) + { + ::odata::utility::uri_builder builder(m_service_root); + builder.append_path(entity_set->get_name() + entity_value->get_entity_key_string()); + if (entity_value->get_value_type()->get_full_name() != entity_set->get_entity_type()->get_full_name()) + { + builder.append_path(entity_value->get_value_type()->get_full_name()); + } + + return builder.to_uri(); + } + + +private: + std::shared_ptr<::odata::edm::edm_model> m_model; + ::odata::utility::uri m_service_root; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_operation.h b/include/odata/core/odata_operation.h new file mode 100644 index 0000000..e46c0b2 --- /dev/null +++ b/include/odata/core/odata_operation.h @@ -0,0 +1,73 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/common/uri.h" + +namespace odata { namespace core +{ + +class odata_operation +{ +public: + odata_operation(::odata::utility::string_t name) : m_name(name), m_title(), m_target() + { + + } + + virtual bool is_function() = 0; + + void set_title(::odata::utility::string_t title) + { + m_title = title; + } + + ::odata::utility::string_t get_title() + { + return m_title; + } + + void set_target(::odata::utility::uri target) + { + m_target = target; + } + + ::odata::utility::uri get_target() + { + return m_target; + } + + ::odata::utility::string_t get_name() + { + return m_name; + } + +private: + ::odata::utility::string_t m_name; + ::odata::utility::string_t m_title; + ::odata::utility::uri m_target; +}; + +class odata_function : public odata_operation +{ +public: + bool is_function() + { + return true; + } +}; + +class odata_action : public odata_operation +{ +public: + bool is_function() + { + return false; + } +}; +}} \ No newline at end of file diff --git a/include/odata/core/odata_orderby_clause.h b/include/odata/core/odata_orderby_clause.h new file mode 100644 index 0000000..56968e8 --- /dev/null +++ b/include/odata/core/odata_orderby_clause.h @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +namespace odata { namespace core +{ + +class odata_orderby_clause +{ +public: + odata_orderby_clause( + std::vector, bool>> &&items, + std::shared_ptr<::odata::core::odata_range_variable> range_variable) + : m_items(items), + m_range_variable(range_variable) {} + ~odata_orderby_clause() {} + + const std::vector, bool>> &items() const + { return m_items; } + const std::pair, bool> &item_at(::size_t i) const + { return m_items[i]; } + std::shared_ptr<::odata::core::odata_range_variable> range_variable() const { return m_range_variable; } + +private: + std::vector, bool>> m_items; + std::shared_ptr<::odata::core::odata_range_variable> m_range_variable; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_parameter.h b/include/odata/core/odata_parameter.h new file mode 100644 index 0000000..ff8967d --- /dev/null +++ b/include/odata/core/odata_parameter.h @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/core/odata_value.h" + +namespace odata { namespace core +{ +// Represents the parameter in function/action. +class odata_parameter +{ +public: + /// Default constructor + odata_parameter() + : m_value(std::make_shared<::odata::core::odata_value>()) + { + } + + odata_parameter(::odata::utility::string_t name, const std::shared_ptr<::odata::core::odata_value>& value) + : m_name(name), m_value(value) + { + } + + const ::odata::utility::string_t& get_name() const { return m_name; } + + void set_name(const ::odata::utility::string_t& name) + { + m_name = name; + } + + const std::shared_ptr<::odata::core::odata_value>& get_value() const { return m_value; } + + void set_value(const std::shared_ptr<::odata::core::odata_value>& value) + { + m_value = value; + } + +private: + ::odata::utility::string_t m_name; + std::shared_ptr<::odata::core::odata_value> m_value; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_path.h b/include/odata/core/odata_path.h new file mode 100644 index 0000000..eb647dc --- /dev/null +++ b/include/odata/core/odata_path.h @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_path_segment.h" + +namespace odata { namespace core +{ + +class odata_path +{ +public: + odata_path(std::vector> segments) + : m_segments(std::move(segments)) {} + ~odata_path() {} + + bool empty() const { return m_segments.empty(); } + + bool single(int segment_type) const + { return m_segments.size() == 1 && m_segments[0]->segment_type() == segment_type; } + + ::size_t size() const { return m_segments.size(); } + + std::shared_ptr segment_at(::size_t i) const { return m_segments[i]; } + + const std::vector> &segments() const { return m_segments; } + + template + std::vector visit_with(std::shared_ptr<::odata::core::odata_path_segment_visitor> visitor) + { + std::vector result; + + for (auto it = m_segments.begin(); it != m_segments.end(); ++it) + { + result.push_back((*it)->accept(visitor)); + } + + return std::move(result); + } + +private: + odata_path(const odata_path &); + odata_path &operator=(const odata_path &); + + std::vector> m_segments; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_path_segment.h b/include/odata/core/odata_path_segment.h new file mode 100644 index 0000000..9b7c37d --- /dev/null +++ b/include/odata/core/odata_path_segment.h @@ -0,0 +1,355 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_primitive_value.h" + +namespace odata { namespace core +{ + +namespace odata_path_segment_type { +enum { + None, + Metadata, + Batch, + EntitySet, + Singleton, + Key, + StructuralProperty, + NavigationProperty, + DynamicProperty, + Value, + Count, + Ref, + Type, + Operation, + OperationImport, +}; +} + +template +class odata_path_segment_visitor; + +class odata_metadata_segment; +class odata_batch_segment; +class odata_entity_set_segment; +class odata_singleton_segment; +class odata_key_segment; +class odata_structural_property_segment; +class odata_navigation_property_segment; +class odata_dynamic_property_segment; +class odata_value_segment; +class odata_count_segment; +class odata_ref_segment; +class odata_type_segment; +class odata_operation_segment; +class odata_operation_import_segment; + +class odata_path_segment : public std::enable_shared_from_this +{ +public: + odata_path_segment() + : m_segment_type(odata_path_segment_type::None) {} + virtual ~odata_path_segment() {} + + int segment_type() const { return m_segment_type; } + + template std::shared_ptr as() + { + return std::static_pointer_cast(shared_from_this()); + } + + ODATACPP_API static std::shared_ptr create_metadata_segment(); + ODATACPP_API static std::shared_ptr create_batch_segment(); + ODATACPP_API static std::shared_ptr create_entity_set_segment(std::shared_ptr<::odata::edm::edm_entity_set> entity_set); + ODATACPP_API static std::shared_ptr create_singleton_segment(std::shared_ptr<::odata::edm::edm_singleton> singleton); + ODATACPP_API static std::shared_ptr create_key_segment( + std::shared_ptr<::odata::edm::edm_navigation_source> navigation_source, + std::shared_ptr<::odata::edm::edm_entity_type> target_entity_type, + std::vector>> keys); + ODATACPP_API static std::shared_ptr create_structural_property_segment( + std::shared_ptr<::odata::edm::edm_structured_type> owning_type, + std::shared_ptr<::odata::edm::edm_property_type> property); + ODATACPP_API static std::shared_ptr create_navigation_property_segment( + std::shared_ptr<::odata::edm::edm_structured_type> owning_type, + std::shared_ptr<::odata::edm::edm_property_type> property, + std::shared_ptr<::odata::edm::edm_navigation_type> navigation_type); + ODATACPP_API static std::shared_ptr create_dynamic_property_segment(const ::odata::utility::string_t &property_name); + ODATACPP_API static std::shared_ptr create_value_segment(); + ODATACPP_API static std::shared_ptr create_count_segment(); + ODATACPP_API static std::shared_ptr create_ref_segment(); + ODATACPP_API static std::shared_ptr create_type_segment(std::shared_ptr<::odata::edm::edm_named_type> type); + ODATACPP_API static std::shared_ptr create_operation_import_segment( + std::shared_ptr<::odata::edm::edm_operation_import> operation_import, + std::vector>> parameters); + ODATACPP_API static std::shared_ptr create_operation_segment( + std::shared_ptr<::odata::edm::edm_operation_type> operation, + std::vector>> parameters); + + template + T accept(std::shared_ptr<::odata::core::odata_path_segment_visitor> visitor) + { + switch (m_segment_type) + { + case odata_path_segment_type::Metadata: + return visitor->visit(this->as<::odata::core::odata_metadata_segment>()); + case odata_path_segment_type::Batch: + return visitor->visit(this->as<::odata::core::odata_batch_segment>()); + case odata_path_segment_type::EntitySet: + return visitor->visit(this->as<::odata::core::odata_entity_set_segment>()); + case odata_path_segment_type::Singleton: + return visitor->visit(this->as<::odata::core::odata_singleton_segment>()); + case odata_path_segment_type::Key: + return visitor->visit(this->as<::odata::core::odata_key_segment>()); + case odata_path_segment_type::StructuralProperty: + return visitor->visit(this->as<::odata::core::odata_structural_property_segment>()); + case odata_path_segment_type::NavigationProperty: + return visitor->visit(this->as<::odata::core::odata_navigation_property_segment>()); + case odata_path_segment_type::DynamicProperty: + return visitor->visit(this->as<::odata::core::odata_dynamic_property_segment>()); + case odata_path_segment_type::Value: + return visitor->visit(this->as<::odata::core::odata_value_segment>()); + case odata_path_segment_type::Count: + return visitor->visit(this->as<::odata::core::odata_count_segment>()); + case odata_path_segment_type::Ref: + return visitor->visit(this->as<::odata::core::odata_ref_segment>()); + case odata_path_segment_type::Type: + return visitor->visit(this->as<::odata::core::odata_type_segment>()); + case odata_path_segment_type::Operation: + return visitor->visit(this->as<::odata::core::odata_operation_segment>()); + case odata_path_segment_type::OperationImport: + return visitor->visit(this->as<::odata::core::odata_operation_import_segment>()); + } + + return visitor->visit_any(shared_from_this()); + } + +protected: + odata_path_segment(int segment_type) + : m_segment_type(segment_type) {} + +private: + // Make odata_path_segment and all its subclasses non-copyable. + odata_path_segment(const odata_path_segment &); + odata_path_segment &operator=(const odata_path_segment &); + + int m_segment_type; +}; + +class odata_metadata_segment : public odata_path_segment +{ +public: + odata_metadata_segment() + : odata_path_segment(odata_path_segment_type::Metadata) {} + ~odata_metadata_segment() {} +}; + +class odata_batch_segment : public odata_path_segment +{ +public: + odata_batch_segment() + : odata_path_segment(odata_path_segment_type::Batch) {} + ~odata_batch_segment() {} +}; + +class odata_entity_set_segment : public odata_path_segment +{ +public: + odata_entity_set_segment(std::shared_ptr<::odata::edm::edm_entity_set> entity_set) + : odata_path_segment(odata_path_segment_type::EntitySet), + m_entity_set(entity_set), + m_entity_type(entity_set->get_entity_type()) {} + ~odata_entity_set_segment() {} + + std::shared_ptr<::odata::edm::edm_entity_set> entity_set() const { return m_entity_set.lock(); } + std::shared_ptr<::odata::edm::edm_entity_type> entity_type() const { return m_entity_type.lock(); } + +private: + std::weak_ptr<::odata::edm::edm_entity_set> m_entity_set; + std::weak_ptr<::odata::edm::edm_entity_type> m_entity_type; +}; + +class odata_singleton_segment : public odata_path_segment +{ +public: + odata_singleton_segment(std::shared_ptr<::odata::edm::edm_singleton> singleton) + : odata_path_segment(odata_path_segment_type::Singleton), + m_singleton(singleton), + m_entity_type(singleton->get_entity_type()) {} + ~odata_singleton_segment() {} + + std::shared_ptr<::odata::edm::edm_singleton> singleton() const { return m_singleton.lock(); } + std::shared_ptr<::odata::edm::edm_entity_type> entity_type() const { return m_entity_type.lock(); } + +private: + std::weak_ptr<::odata::edm::edm_singleton> m_singleton; + std::weak_ptr<::odata::edm::edm_entity_type> m_entity_type; +}; + +class odata_key_segment : public odata_path_segment +{ +public: + odata_key_segment( + std::shared_ptr<::odata::edm::edm_navigation_source> navigation_source, + std::shared_ptr<::odata::edm::edm_entity_type> target_entity_type, + std::vector>> &&keys) + : odata_path_segment(odata_path_segment_type::Key), + m_navigation_source(navigation_source), + m_target_entity_type(target_entity_type), + m_keys(keys) {} + ~odata_key_segment() {} + + std::shared_ptr<::odata::edm::edm_navigation_source> navigation_source() const { return m_navigation_source.lock(); } + std::shared_ptr<::odata::edm::edm_entity_type> target_entity_type() const { return m_target_entity_type.lock(); } + + const std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_primitive_value>> &key_at(::size_t i) const { return m_keys[i]; } + const std::vector>> &keys() const { return m_keys; } + +private: + std::weak_ptr<::odata::edm::edm_navigation_source> m_navigation_source; + std::weak_ptr<::odata::edm::edm_entity_type> m_target_entity_type; + std::vector>> m_keys; +}; + +class odata_structural_property_segment : public odata_path_segment +{ +public: + odata_structural_property_segment( + std::shared_ptr<::odata::edm::edm_structured_type> owning_type, + std::shared_ptr<::odata::edm::edm_property_type> property) + : odata_path_segment(odata_path_segment_type::StructuralProperty), + m_owning_type(owning_type), + m_property(property) {} + ~odata_structural_property_segment() {} + + std::shared_ptr<::odata::edm::edm_structured_type> owning_type() const { return m_owning_type.lock(); } + std::shared_ptr<::odata::edm::edm_property_type> property() const { return m_property.lock(); } + +private: + std::weak_ptr<::odata::edm::edm_structured_type> m_owning_type; + std::weak_ptr<::odata::edm::edm_property_type> m_property; +}; + +class odata_navigation_property_segment : public odata_path_segment +{ +public: + odata_navigation_property_segment( + std::shared_ptr<::odata::edm::edm_structured_type> owning_type, + std::shared_ptr<::odata::edm::edm_property_type> property, + std::shared_ptr<::odata::edm::edm_navigation_type> navigation_type) + : odata_path_segment(odata_path_segment_type::NavigationProperty), + m_owning_type(owning_type), + m_property(property), + m_navigation_type(navigation_type) {} + ~odata_navigation_property_segment() {} + + std::shared_ptr<::odata::edm::edm_structured_type> owning_type() const { return m_owning_type.lock(); } + std::shared_ptr<::odata::edm::edm_property_type> property() const { return m_property.lock(); } + std::shared_ptr<::odata::edm::edm_navigation_type> navigation_type() const { return m_navigation_type.lock(); } + +private: + std::weak_ptr<::odata::edm::edm_structured_type> m_owning_type; + std::weak_ptr<::odata::edm::edm_property_type> m_property; + std::weak_ptr<::odata::edm::edm_navigation_type> m_navigation_type; +}; + +class odata_dynamic_property_segment : public odata_path_segment +{ +public: + odata_dynamic_property_segment( + const ::odata::utility::string_t &property_name) + : odata_path_segment(odata_path_segment_type::DynamicProperty), + m_property_name(property_name) {} + ~odata_dynamic_property_segment() {} + + const ::odata::utility::string_t &property_name() const { return m_property_name; } + +private: + ::odata::utility::string_t m_property_name; +}; + +class odata_value_segment : public odata_path_segment +{ +public: + odata_value_segment() + : odata_path_segment(odata_path_segment_type::Value) {} + ~odata_value_segment() {} +}; + +class odata_count_segment : public odata_path_segment +{ +public: + odata_count_segment() + : odata_path_segment(odata_path_segment_type::Count) {} + ~odata_count_segment() {} +}; + +class odata_ref_segment : public odata_path_segment +{ +public: + odata_ref_segment() + : odata_path_segment(odata_path_segment_type::Ref) {} + ~odata_ref_segment() {} +}; + +class odata_type_segment : public odata_path_segment +{ +public: + odata_type_segment(std::shared_ptr<::odata::edm::edm_named_type> type) + : odata_path_segment(odata_path_segment_type::Type), + m_type(type) {} + ~odata_type_segment() {} + + std::shared_ptr<::odata::edm::edm_named_type> type() const { return m_type.lock(); } + +private: + std::weak_ptr<::odata::edm::edm_named_type> m_type; +}; + +class odata_operation_segment : public odata_path_segment +{ +public: + odata_operation_segment( + std::shared_ptr<::odata::edm::edm_operation_type> operation, + std::vector>> &¶meters) + : odata_path_segment(odata_path_segment_type::Operation), + m_operation(operation), + m_parameters(parameters) {} + ~odata_operation_segment() {} + + std::shared_ptr<::odata::edm::edm_operation_type> operation() const { return m_operation.lock(); } + const std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_primitive_value>> ¶meter_at(::size_t i) const { return m_parameters[i]; } + const std::vector>> ¶meters() const { return m_parameters; } + +private: + std::weak_ptr<::odata::edm::edm_operation_type> m_operation; + std::vector>> m_parameters; +}; + +class odata_operation_import_segment : public odata_path_segment +{ +public: + odata_operation_import_segment( + std::shared_ptr<::odata::edm::edm_operation_import> operation_import, + std::vector>> &¶meters) + : odata_path_segment(odata_path_segment_type::OperationImport), + m_operation_import(operation_import), + m_parameters(parameters) {} + ~odata_operation_import_segment() {} + + std::shared_ptr<::odata::edm::edm_operation_import> operation_import() const { return m_operation_import.lock(); } + const std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_primitive_value>> ¶meter_at(::size_t i) const { return m_parameters[i]; } + const std::vector>> ¶meters() const { return m_parameters; } + +private: + std::weak_ptr<::odata::edm::edm_operation_import> m_operation_import; + std::vector>> m_parameters; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_path_segment_visitor.h b/include/odata/core/odata_path_segment_visitor.h new file mode 100644 index 0000000..0d2c74d --- /dev/null +++ b/include/odata/core/odata_path_segment_visitor.h @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_path_segment.h" + +namespace odata { namespace core +{ + +class odata_path_segment; +class odata_metadata_segment; + +template +class odata_path_segment_visitor +{ +public: + odata_path_segment_visitor() {} + virtual ~odata_path_segment_visitor() {} + + virtual T visit(std::shared_ptr<::odata::core::odata_metadata_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_batch_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_entity_set_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_singleton_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_key_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_structural_property_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_navigation_property_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_dynamic_property_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_value_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_count_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_ref_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_type_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_operation_segment> segment) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_operation_import_segment> segment) = 0; + virtual T visit_any(std::shared_ptr<::odata::core::odata_path_segment> segment) = 0; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_primitive_value.h b/include/odata/core/odata_primitive_value.h new file mode 100644 index 0000000..5dee4e0 --- /dev/null +++ b/include/odata/core/odata_primitive_value.h @@ -0,0 +1,112 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_value.h" + +namespace odata { namespace core +{ + +class odata_primitive_value : public odata_value +{ +public: + odata_primitive_value(std::shared_ptr<::odata::edm::edm_named_type>type, const ::odata::utility::string_t& stringRep) : odata_value(type), m_string_rep(stringRep) + { + } + + static std::shared_ptr make_primitive_value(int16_t int16_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::INT16(), ::odata::utility::conversions::print_string(int16_value)); + } + + static std::shared_ptr make_primitive_value(int32_t int32_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(int32_value)); + } + + static std::shared_ptr make_primitive_value(const std::vector& binary_data) + { + return std::make_shared(::odata::edm::edm_primitive_type::BINARY(), ::odata::utility::conversions::to_base64(binary_data)); + } + + static std::shared_ptr make_primitive_value(bool boolean_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::BOOLEAN(), ::odata::utility::conversions::print_string(boolean_value)); + } + + static std::shared_ptr make_primitive_value(::odata::utility::datetime datetime_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::DATETIMEOFFSET(), datetime_value.to_string(::odata::utility::datetime::date_format::ISO_8601)); + } + + static std::shared_ptr make_primitive_value(::odata::utility::seconds duration_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::DURATION(), ::odata::utility::timespan::seconds_to_xml_duration(duration_value)); + } + + static std::shared_ptr make_primitive_value(double double_precision_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::DOUBLE(), ::odata::utility::print_double(double_precision_value)); + } + + static std::shared_ptr make_primitive_value(float float_precision_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::SINGLE(), ::odata::utility::print_float(float_precision_value)); + } + + static std::shared_ptr make_primitive_value(int64_t int64_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::INT64(), ::odata::utility::conversions::print_string(int64_value)); + } + + static std::shared_ptr make_primitive_value(uint64_t u_int64_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::INT64(), ::odata::utility::conversions::print_string(u_int64_value)); + } + + static std::shared_ptr make_primitive_value(const ::odata::utility::string_t& string_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::STRING(), string_value); + } + + static std::shared_ptr make_primitive_value(const ::odata::utility::char_t* string_value) + { + return std::make_shared(::odata::edm::edm_primitive_type::STRING(), string_value); + } + + const ::odata::utility::string_t& to_string() const + { + return m_string_rep; + } + + template T as() + { + T result; + _try_get(result); + return result; + } + +private: + ::odata::utility::string_t m_string_rep; + + ODATACPP_API void _try_get(std::vector& value) const; + ODATACPP_API void _try_get(unsigned char& value) const; + ODATACPP_API void _try_get(char& value) const; + ODATACPP_API void _try_get(bool& value) const; + ODATACPP_API void _try_get(::odata::utility::datetime& value) const; + ODATACPP_API void _try_get(::odata::utility::seconds& value) const; + ODATACPP_API void _try_get(double& value) const; + ODATACPP_API void _try_get(float& value) const; + ODATACPP_API void _try_get(int16_t& value) const; + ODATACPP_API void _try_get(int32_t& value) const; + ODATACPP_API void _try_get(int64_t& value) const; + ODATACPP_API void _try_get(uint64_t& value) const; + ODATACPP_API void _try_get(::odata::utility::string_t& value) const; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_property_map.h b/include/odata/core/odata_property_map.h new file mode 100644 index 0000000..80371d9 --- /dev/null +++ b/include/odata/core/odata_property_map.h @@ -0,0 +1,154 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" + +namespace odata { namespace core +{ + +class odata_value; +class odata_complex_value; +class odata_entity_value; +class odata_primitive_value; +class odata_enum_value; +class odata_collection_value; + +typedef ::odata::utility::string_t odata_property_key; +typedef ::odata::utility::string_t odata_property_name; +typedef std::unordered_map> odata_properties; + +/// +/// A type that holds the properties for an entity or a complex. +/// Key (odata_property_name) - The property name +/// Value (property_value) - see above +/// +class odata_property_map +{ +public: + odata_property_map() + { + } + + odata_property_map(odata_property_map& property_map) + { + m_properties = std::move(property_map.m_properties); + } + + odata_property_map(odata_property_map&& property_map) + { + m_properties = std::move(property_map.m_properties); + } + + odata_property_map(const odata_property_map& property_map) + { + m_properties = std::move(property_map.m_properties); + } + + odata_property_map(const odata_property_map&& property_map) + { + m_properties = std::move(property_map.m_properties); + } + + odata_property_map& operator = (odata_property_map&& property_map) + { + m_properties = std::move(property_map.m_properties); + return *this; + } + + odata_property_map& operator = (odata_property_map& property_map) + { + m_properties = std::move(property_map.m_properties); + return *this; + } + + odata_property_map& operator = (const odata_property_map&& property_map) + { + m_properties = std::move(property_map.m_properties); + return *this; + } + + odata_property_map& operator = (const odata_property_map& property_map) + { + m_properties = std::move(property_map.m_properties); + return *this; + } + + ODATACPP_API std::shared_ptr& operator[](odata_property_name&& _Keyval) + { + return m_properties[_Keyval]; + } + + ODATACPP_API std::shared_ptr& operator[](odata_property_name& _Keyval) + { + return m_properties[_Keyval]; + } + + ODATACPP_API std::shared_ptr& operator[](const odata_property_name& _Keyval) + { + return m_properties[_Keyval]; + } + + ODATACPP_API odata_properties::iterator erase(odata_properties::const_iterator _Where) + { + return m_properties.erase(_Where); + } + + ODATACPP_API odata_properties::size_type erase(const odata_property_name& _Keyval) + { + return m_properties.erase(_Keyval); + } + + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, char& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, unsigned char& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, std::vector& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, bool& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, ::odata::utility::datetime& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, ::odata::utility::seconds& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, double& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, float& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, int32_t& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, int16_t& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, int64_t& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, ::odata::utility::string_t& value) const; + ODATACPP_API bool try_get(const ::odata::utility::string_t& property_name, odata_property_map& value) const; + + /// + /// Gets the beginning const_iterator of the properties + /// + odata_properties::const_iterator cbegin() const + { + return m_properties.cbegin(); + } + + /// + /// Gets the end const_iterator of the properties + /// + odata_properties::const_iterator cend() const + { + return m_properties.cend(); + } + + int size() const + { + return m_properties.size(); + } + +private: + friend class entity; + odata_properties m_properties; + + ODATACPP_API std::pair> find_property(const ::odata::utility::string_t& property_name) const; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_query_node.h b/include/odata/core/odata_query_node.h new file mode 100644 index 0000000..16ce05c --- /dev/null +++ b/include/odata/core/odata_query_node.h @@ -0,0 +1,343 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_primitive_value.h" + +namespace odata { namespace core +{ + +namespace odata_query_node_kind { +enum +{ + None, + Constant, + BinaryOperator, + UnaryOperator, + ParameterAlias, + PropertyAccess, + TypeCast, + Lambda, + RangeVariable, + FunctionCall +}; +} + +template +class odata_query_node_visitor; + +class odata_constant_node; +class odata_binary_operator_node; +class odata_unary_operator_node; +class odata_parameter_alias_node; +class odata_property_access_node; +class odata_type_cast_node; +class odata_lambda_node; +class odata_range_variable_node; +class odata_function_call_node; + +class odata_query_node : public std::enable_shared_from_this +{ +public: + odata_query_node() + : m_node_kind(odata_query_node_kind::None) {} + virtual ~odata_query_node() {} + + int node_kind() const { return m_node_kind; } + + template + std::shared_ptr as() + { + return std::static_pointer_cast(shared_from_this()); + } + + ODATACPP_API static std::shared_ptr create_constant_node(std::shared_ptr<::odata::core::odata_primitive_value> value); + ODATACPP_API static std::shared_ptr create_operator_or_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_and_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_eq_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_ne_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_gt_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_ge_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_lt_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_le_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_has_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_add_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_sub_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_mul_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_div_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_mod_node(std::shared_ptr left, std::shared_ptr right); + ODATACPP_API static std::shared_ptr create_operator_not_node(std::shared_ptr operand); + ODATACPP_API static std::shared_ptr create_operator_neg_node(std::shared_ptr operand); + ODATACPP_API static std::shared_ptr create_parameter_alias_node(const ::odata::utility::string_t &alias); + ODATACPP_API static std::shared_ptr create_property_access_node( + const ::odata::utility::string_t &property_name, + std::shared_ptr parent); + ODATACPP_API static std::shared_ptr create_type_cast_node( + const ::odata::utility::string_t &type_name, + std::shared_ptr parent); + ODATACPP_API static std::shared_ptr create_lambda_node( + bool is_any, + std::shared_ptr expression, + const ::odata::utility::string_t ¶meter, + std::shared_ptr parent); + ODATACPP_API static std::shared_ptr create_range_variable_node(const ::odata::utility::string_t &name); + ODATACPP_API static std::shared_ptr create_function_call_node( + const ::odata::utility::string_t &name, + std::vector>> parameters); + + template + T accept(std::shared_ptr<::odata::core::odata_query_node_visitor> visitor) + { + switch (m_node_kind) + { + case odata_query_node_kind::Constant: + return visitor->visit(as<::odata::core::odata_constant_node>()); + case odata_query_node_kind::BinaryOperator: + return visitor->visit(as<::odata::core::odata_binary_operator_node>()); + case odata_query_node_kind::UnaryOperator: + return visitor->visit(as<::odata::core::odata_unary_operator_node>()); + case odata_query_node_kind::ParameterAlias: + return visitor->visit(as<::odata::core::odata_parameter_alias_node>()); + case odata_query_node_kind::PropertyAccess: + return visitor->visit(as<::odata::core::odata_property_access_node>()); + case odata_query_node_kind::TypeCast: + return visitor->visit(as<::odata::core::odata_type_cast_node>()); + case odata_query_node_kind::Lambda: + return visitor->visit(as<::odata::core::odata_lambda_node>()); + case odata_query_node_kind::RangeVariable: + return visitor->visit(as<::odata::core::odata_range_variable_node>()); + case odata_query_node_kind::FunctionCall: + return visitor->visit(as<::odata::core::odata_function_call_node>()); + } + + return visitor->visit_any(shared_from_this()); + } + +protected: + odata_query_node(int kind) + : m_node_kind(kind) {} + +private: + // Make odata_query_node and all its subclasses non-copyable. + odata_query_node(const odata_query_node &); + odata_query_node &operator=(const odata_query_node &); + + int m_node_kind; +}; + +//class odata_single_value_node : public odata_query_node +//{ +//public: +// odata_single_value_node(int node_kind, std::shared_ptr<::odata::edm::edm_named_type> value_type) +// : odata_query_node(node_kind), +// m_value_type(value_type) {} +// virtual ~odata_single_value_node() {} +// +// std::shared_ptr<::odata::edm::edm_named_type> value_type() const { return m_value_type.lock(); } +// +// void set_value_type(std::shared_ptr<::odata::edm::edm_named_type> value_type) +// { m_value_type = value_type; } +// +//private: +// std::weak_ptr<::odata::edm::edm_named_type> m_value_type; +//}; + +class odata_constant_node : public odata_query_node +{ +public: + odata_constant_node(std::shared_ptr<::odata::core::odata_primitive_value> value) + : odata_query_node(odata_query_node_kind::Constant), + m_value(value) {} + ~odata_constant_node() {} + + std::shared_ptr<::odata::core::odata_primitive_value> value() const { return m_value; } + +private: + std::shared_ptr<::odata::core::odata_primitive_value> m_value; +}; + +namespace binary_operator_kind { +enum { + Or, + And, + Equal, + NotEqual, + GreaterThan, + GreaterThanOrEqual, + LessThan, + LessThanOrEqual, + Has, + Add, + Sub, + Multiply, + Divide, + Modulo +}; +} + +class odata_binary_operator_node : public odata_query_node +{ +public: + odata_binary_operator_node(int operator_kind, std::shared_ptr left, std::shared_ptr right) + : odata_query_node(odata_query_node_kind::BinaryOperator), + m_operator_kind(operator_kind), + m_left(left), + m_right(right) {} + ~odata_binary_operator_node() {} + + int operator_kind() const { return m_operator_kind; } + std::shared_ptr left() const { return m_left; } + std::shared_ptr right() const { return m_right; } + +private: + int m_operator_kind; + std::shared_ptr m_left; + std::shared_ptr m_right; +}; + +namespace unary_operator_kind { +enum { + Negate, + Not +}; +} + +class odata_unary_operator_node : public odata_query_node +{ +public: + odata_unary_operator_node(int operator_kind, std::shared_ptr operand) + : odata_query_node(odata_query_node_kind::UnaryOperator), + m_operator_kind(operator_kind), + m_operand(operand) {} + ~odata_unary_operator_node() {} + + int operator_kind() const { return m_operator_kind; } + std::shared_ptr operand() const { return m_operand; } + +private: + int m_operator_kind; + std::shared_ptr m_operand; +}; + +class odata_parameter_alias_node : public odata_query_node +{ +public: + odata_parameter_alias_node(const ::odata::utility::string_t &alias) + : odata_query_node(odata_query_node_kind::ParameterAlias), + m_alias(alias) {} + ~odata_parameter_alias_node() {} + + const ::odata::utility::string_t &alias() const { return m_alias; } + +private: + ::odata::utility::string_t m_alias; +}; + +class odata_property_access_node : public odata_query_node +{ +public: + odata_property_access_node( + const ::odata::utility::string_t &property_name, + std::shared_ptr parent) + : odata_query_node(odata_query_node_kind::PropertyAccess), + m_property_name(property_name), + m_parent(parent) {} + ~odata_property_access_node() {} + + const ::odata::utility::string_t &property_name() const { return m_property_name; } + std::shared_ptr parent() const { return m_parent; } + +private: + ::odata::utility::string_t m_property_name; + std::shared_ptr m_parent; +}; + +class odata_type_cast_node : public odata_query_node +{ +public: + odata_type_cast_node( + const ::odata::utility::string_t &type_name, + std::shared_ptr parent) + : odata_query_node(odata_query_node_kind::TypeCast), + m_type_name(type_name), + m_parent(parent) {} + ~odata_type_cast_node() {} + + const ::odata::utility::string_t &type_name() const { return m_type_name; } + std::shared_ptr parent() const { return m_parent; } + +private: + ::odata::utility::string_t m_type_name; + std::shared_ptr m_parent; +}; + +class odata_lambda_node : public odata_query_node +{ +public: + odata_lambda_node( + bool is_any, + std::shared_ptr expression, + const ::odata::utility::string_t ¶meter, + std::shared_ptr parent) + : odata_query_node(odata_query_node_kind::Lambda), + m_is_any(is_any), + m_expression(expression), + m_parameter(parameter), + m_parent(parent) {} + ~odata_lambda_node() {} + + bool is_any() const { return m_is_any; } + std::shared_ptr expression() const { return m_expression; } + const ::odata::utility::string_t ¶meter() const { return m_parameter; } + std::shared_ptr parent() const { return m_parent; } + +private: + std::shared_ptr m_expression; + ::odata::utility::string_t m_parameter; + std::shared_ptr m_parent; + bool m_is_any; +}; + +class odata_range_variable_node : public odata_query_node +{ +public: + odata_range_variable_node(const ::odata::utility::string_t &name) + : odata_query_node(odata_query_node_kind::RangeVariable), + m_name(name) {} + ~odata_range_variable_node() {} + + const ::odata::utility::string_t &name() const { return m_name; } + +private: + ::odata::utility::string_t m_name; +}; + +class odata_function_call_node : public odata_query_node +{ +public: + odata_function_call_node( + const ::odata::utility::string_t &name, + std::vector>> &¶meters) + : odata_query_node(odata_query_node_kind::FunctionCall), + m_name(name), + m_parameters(parameters) {} + ~odata_function_call_node() {} + + const ::odata::utility::string_t &name() const { return m_name; } + const std::pair<::odata::utility::string_t, std::shared_ptr> ¶meter_at(::size_t i) const + { return m_parameters[i]; } + const std::vector>> ¶meters() const + { return m_parameters; } + +private: + ::odata::utility::string_t m_name; + std::vector>> m_parameters; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_query_node_visitor.h b/include/odata/core/odata_query_node_visitor.h new file mode 100644 index 0000000..dee241c --- /dev/null +++ b/include/odata/core/odata_query_node_visitor.h @@ -0,0 +1,34 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_query_node.h" + +namespace odata { namespace core +{ + +template +class odata_query_node_visitor : public std::enable_shared_from_this> +{ +public: + odata_query_node_visitor() {} + virtual ~odata_query_node_visitor() {} + + virtual T visit(std::shared_ptr<::odata::core::odata_constant_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_binary_operator_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_unary_operator_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_parameter_alias_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_property_access_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_type_cast_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_lambda_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_range_variable_node> node) = 0; + virtual T visit(std::shared_ptr<::odata::core::odata_function_call_node> node) = 0; + virtual T visit_any(std::shared_ptr<::odata::core::odata_query_node> node) = 0; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_search_clause.h b/include/odata/core/odata_search_clause.h new file mode 100644 index 0000000..3b4b8c4 --- /dev/null +++ b/include/odata/core/odata_search_clause.h @@ -0,0 +1,25 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +namespace odata { namespace core +{ + +class odata_search_clause +{ +public: + odata_search_clause(std::shared_ptr<::odata::core::odata_query_node> expression) + : m_expression(expression) {} + ~odata_search_clause() {} + + std::shared_ptr<::odata::core::odata_query_node> expression() const { return m_expression; } + +private: + std::shared_ptr<::odata::core::odata_query_node> m_expression; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_select_expand_clause.h b/include/odata/core/odata_select_expand_clause.h new file mode 100644 index 0000000..a2a8df8 --- /dev/null +++ b/include/odata/core/odata_select_expand_clause.h @@ -0,0 +1,42 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" + +namespace odata { namespace core +{ + +class odata_select_item; +class odata_expand_item; + +class odata_select_expand_clause +{ +public: + odata_select_expand_clause( + std::vector> &&select_items, + std::vector> &&expand_items) + : m_select_items(select_items), + m_expand_items(expand_items) {} + ~odata_select_expand_clause() {} + + const std::vector> &select_items() const + { return m_select_items; } + std::shared_ptr<::odata::core::odata_select_item> select_item_at(::size_t i) const + { return m_select_items[i]; } + + const std::vector> &expand_items() const + { return m_expand_items; } + std::shared_ptr<::odata::core::odata_expand_item> expand_item_at(::size_t i) const + { return m_expand_items[i]; } + +private: + std::vector> m_select_items; + std::vector> m_expand_items; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_service_document.h b/include/odata/core/odata_service_document.h new file mode 100644 index 0000000..a702010 --- /dev/null +++ b/include/odata/core/odata_service_document.h @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/core/odata_service_document_element.h" + +namespace odata { namespace core +{ + +class odata_service_document +{ +public: + odata_service_document() + { + } + + void add_service_document_element(std::shared_ptr element) + { + m_elements.push_back(element); + } + + /// + /// Gets the beginning const_iterator of the properties + /// + std::vector>::const_iterator cbegin() const + { + return m_elements.cbegin(); + } + + /// + /// Gets the end const_iterator of the properties + /// + std::vector>::const_iterator cend() const + { + return m_elements.cend(); + } + + int size() const + { + return m_elements.size(); + } + +private: + std::vector> m_elements; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_service_document_element.h b/include/odata/core/odata_service_document_element.h new file mode 100644 index 0000000..9ec8a09 --- /dev/null +++ b/include/odata/core/odata_service_document_element.h @@ -0,0 +1,50 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" + +namespace odata { namespace core +{ + +enum odata_service_document_element_kind +{ + ENTITY_SET, + SINGLETON, + FUNCTION_IMPORT, +}; + +class odata_service_document_element +{ +public: + odata_service_document_element(::odata::utility::string_t url, ::odata::utility::string_t name, odata_service_document_element_kind kind) : + m_url(url), m_name(name), m_kind(kind) + { + } + + ::odata::utility::string_t get_url() + { + return m_url; + } + + ::odata::utility::string_t get_name() + { + return m_name; + } + + odata_service_document_element_kind get_kind() + { + return m_kind; + } + +private: + ::odata::utility::string_t m_url; + ::odata::utility::string_t m_name; + odata_service_document_element_kind m_kind; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_structured_value.h b/include/odata/core/odata_structured_value.h new file mode 100644 index 0000000..c97af9c --- /dev/null +++ b/include/odata/core/odata_structured_value.h @@ -0,0 +1,171 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_value.h" +#include "odata/core/odata_primitive_value.h" + +namespace odata { namespace core +{ +class odata_collection_value; +class odata_entity_value; +class odata_complex_value; +class odata_enum_value; + +class odata_structured_value : public odata_value +{ +public: + odata_structured_value(std::shared_ptr<::odata::edm::edm_named_type>type) : odata_value(type) + { + } + + odata_structured_value(std::shared_ptr<::odata::edm::edm_named_type>type, odata_property_map properties) + : m_properties(std::move(properties)), odata_value(type) + { + + } + + virtual ~odata_structured_value(){} + + /// Returns a const reference to the property map. + const odata_property_map& properties() const { return m_properties; } + + void set_properties(odata_property_map map) + { + m_properties = std::move(map); + } + + /// Tells whether the structured object has a property of a given name. + /// The name of the property + bool has_property(const ::odata::utility::string_t& property_name) const + { + std::shared_ptr details; + return m_properties.try_get(property_name, details); + } + + /// Tells whether the structured object has a property of a given name. + /// The name of the property + bool has_property(const ::odata::utility::string_t& property_name, std::shared_ptr<::odata::edm::edm_named_type>& type) const + { + std::shared_ptr details; + bool result = m_properties.try_get(property_name, details); + if ( result ) + { + type = details->get_value_type(); + } + return result; + } + + bool get_property_value(const ::odata::utility::string_t& property_name, std::shared_ptr& property_value) + { + return m_properties.try_get(property_name, property_value); + } + + template + bool try_get(const ::odata::utility::string_t& property_name, T& value) const + { + return m_properties.try_get(property_name, value); + } + + void set_value(const ::odata::utility::string_t& property_name, float float_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::SINGLE(), ::odata::utility::print_float(float_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, unsigned char ubyte_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::BYTE(), ::odata::utility::conversions::print_string(ubyte_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, char byte_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::BYTE(), ::odata::utility::conversions::print_string(byte_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, int32_t int32_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(int32_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, const std::vector& binary_data) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::BINARY(), ::odata::utility::conversions::to_base64(binary_data)); + } + + void set_value(const ::odata::utility::string_t& property_name, bool boolean_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::BOOLEAN(), ::odata::utility::conversions::print_string(boolean_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, ::odata::utility::datetime datetime_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::DATETIMEOFFSET(), datetime_value.to_string(::odata::utility::datetime::date_format::ISO_8601)); + } + + void set_value(const ::odata::utility::string_t& property_name, ::odata::utility::seconds duration_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::DURATION(), ::odata::utility::timespan::seconds_to_xml_duration(duration_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, double double_precision_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::DOUBLE(), ::odata::utility::print_double(double_precision_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, int64_t int64_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::INT64(), ::odata::utility::conversions::print_string(int64_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, uint64_t u_int64_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::INT64(), ::odata::utility::conversions::print_string(u_int64_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, int16_t int16_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::INT16(), ::odata::utility::conversions::print_string(int16_value)); + } + + void set_value(const ::odata::utility::string_t& property_name, const ::odata::utility::char_t* string_value) + { + m_properties[property_name] = std::make_shared(::odata::edm::edm_primitive_type::STRING(), string_value); + } + + void set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value) + { + m_properties[property_name] = property_value; + } + + ODATACPP_API void set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value); + + ODATACPP_API void set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value); + + ODATACPP_API void set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value); + + ODATACPP_API void set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value); + + ODATACPP_API void set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value); + + ODATACPP_API void set_value(const ::odata::utility::string_t& property_name, const ::odata::utility::string_t& string_value); + + /// + /// Removes a property from the structured object if it is present. + /// + /// A string representing the property value. + odata_structured_value& remove(const ::odata::utility::string_t& property_name) + { + m_properties.erase(property_name); + return *this; + } + +protected: + odata_property_map m_properties; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_uri.h b/include/odata/core/odata_uri.h new file mode 100644 index 0000000..0af0378 --- /dev/null +++ b/include/odata/core/odata_uri.h @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/core/odata_path.h" +#include "odata/core/odata_select_expand_clause.h" +#include "odata/core/odata_filter_clause.h" +#include "odata/core/odata_select_expand_clause.h" +#include "odata/core/odata_orderby_clause.h" +#include "odata/core/odata_search_clause.h" +#include "odata/common/nullable.h" + +namespace odata { namespace core +{ + +class odata_uri +{ +public: + odata_uri( + std::shared_ptr<::odata::core::odata_path> path, + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause, + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause, + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause, + std::shared_ptr<::odata::core::odata_search_clause> search_clause, + ::odata::common::nullable top, + ::odata::common::nullable skip, + ::odata::common::nullable count) + : m_path(path), + m_select_expand_clause(select_expand_clause), + m_filter_clause(filter_clause), + m_orderby_clause(orderby_clause), + m_search_clause(search_clause), + m_top(top), + m_skip(skip), + m_count(count) {} + ~odata_uri() {} + + static std::shared_ptr create_uri( + std::shared_ptr<::odata::core::odata_path> path, + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause, + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause, + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause, + std::shared_ptr<::odata::core::odata_search_clause> search_clause, + ::odata::common::nullable top, + ::odata::common::nullable skip, + ::odata::common::nullable count); + + std::shared_ptr<::odata::core::odata_path> path() const { return m_path; } + + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause() const { return m_select_expand_clause; } + + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause() const { return m_filter_clause; } + + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause() const { return m_orderby_clause; } + + std::shared_ptr<::odata::core::odata_search_clause> search_clause() const { return m_search_clause; } + + ::odata::common::nullable top() const { return m_top; } + + ::odata::common::nullable skip() const { return m_skip; } + + ::odata::common::nullable count() const { return m_count; } + + bool is_service_document() const { return m_path->empty(); } + + bool is_metadata_document() const { return m_path->single(::odata::core::odata_path_segment_type::Metadata); } + +private: + std::shared_ptr<::odata::core::odata_path> m_path; + std::shared_ptr<::odata::core::odata_select_expand_clause> m_select_expand_clause; + std::shared_ptr<::odata::core::odata_filter_clause> m_filter_clause; + std::shared_ptr<::odata::core::odata_orderby_clause> m_orderby_clause; + std::shared_ptr<::odata::core::odata_search_clause> m_search_clause; + + ::odata::common::nullable m_top; + ::odata::common::nullable m_skip; + ::odata::common::nullable m_count; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_uri_parser.h b/include/odata/core/odata_uri_parser.h new file mode 100644 index 0000000..d98334c --- /dev/null +++ b/include/odata/core/odata_uri_parser.h @@ -0,0 +1,485 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/common/nullable.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_utility.h" +#include "odata/core/odata_primitive_value.h" +#include "odata/core/odata_path.h" +#include "odata/core/odata_select_expand_clause.h" +#include "odata/core/odata_filter_clause.h" +#include "odata/core/odata_orderby_clause.h" +#include "odata/core/odata_search_clause.h" +#include "odata/core/odata_uri.h" +#include "odata/core/odata_exception.h" +#include "odata/core/odata_query_node.h" + +namespace odata { namespace core +{ + +class odata_path_parser +{ +public: + odata_path_parser(std::shared_ptr<::odata::edm::edm_model> model) + : m_model(model) {} + ~odata_path_parser() {} + + std::shared_ptr<::odata::core::odata_path> parse_path(const ::odata::utility::string_t& path); + + std::shared_ptr<::odata::edm::edm_named_type> target_type() const { return m_target_type; } + + std::shared_ptr<::odata::edm::edm_navigation_source> target_navigation_source() const + { return m_target_navigation_source.lock(); } + +private: + odata_path_parser(const odata_path_parser &); + odata_path_parser &operator=(const odata_path_parser &); + + std::shared_ptr<::odata::edm::edm_model> model() const { return m_model.lock(); } + + std::shared_ptr<::odata::core::odata_path_segment> previous_segment() const + { return m_bound_segments.empty() ? nullptr : m_bound_segments[m_bound_segments.size() - 1]; } + + static std::vector>> parse_named_values(const ::odata::utility::string_t &exp); + static void extract_segment_identifier_and_parenthesis_exp(const ::odata::utility::string_t& segment_str, ::odata::utility::string_t& identifier, ::odata::utility::string_t& parenthesis_exp); + + void bind_path(std::vector<::odata::utility::string_t> raw_segments); + void bind_first_segment(const ::odata::utility::string_t &segment_str); + void bind_next_segment(const ::odata::utility::string_t &segment_str); + + bool try_bind_as_navigation_source(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &key_exp); + bool try_bind_as_operation_import(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t ¶meters_exp); + bool try_bind_as_value(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp); + bool try_bind_as_count(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp); + bool try_bind_as_ref(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp); + bool try_bind_as_declared_property(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &key_exp); + bool try_bind_as_type(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp); + bool try_bind_as_operation(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t ¶meters_exp); + + void bind_as_key(const ::odata::utility::string_t &key_exp); + void bind_as_dynamic_property(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp); + + std::shared_ptr<::odata::edm::edm_navigation_source> get_target_navigation_source(); + std::shared_ptr<::odata::edm::edm_named_type> get_target_type(); + void compute_target_navigation_source_and_type(); + + static void validate_is_nonempty_relative_uri(const ::odata::utility::string_t& uri); + static void validate_parameters_match( + const std::vector>> ¶meters_parsed, + const std::vector> ¶meters_from_model, + bool has_binding_parameter = false); + void validate_can_bind_next_segment(); + +private: + std::weak_ptr<::odata::edm::edm_model> m_model; + std::vector> m_bound_segments; + + // Use shared_ptr here because we may create a collection type which is not referenced in the model. + std::shared_ptr<::odata::edm::edm_named_type> m_target_type; + + std::weak_ptr<::odata::edm::edm_navigation_source> m_target_navigation_source; +}; + +class odata_range_variable +{ +public: + odata_range_variable(const ::odata::utility::string_t &name, std::shared_ptr<::odata::edm::edm_named_type> target_type) + : m_name(name), + m_target_type(target_type) {} + + odata_range_variable( + const ::odata::utility::string_t &name, + std::shared_ptr<::odata::edm::edm_named_type> target_type, + std::shared_ptr<::odata::edm::edm_navigation_source> target_navigation_source) + : m_name(name), + m_target_type(target_type), + m_target_navigation_source(target_navigation_source) {} + + const ::odata::utility::string_t &name() const { return m_name; } + std::shared_ptr<::odata::edm::edm_named_type> target_type() const { return m_target_type.lock(); } + std::shared_ptr<::odata::edm::edm_navigation_source> target_navigation_source() const + { return m_target_navigation_source.lock(); } + +private: + odata_range_variable(const odata_range_variable &); + odata_range_variable &operator=(const odata_range_variable &); + +private: + ::odata::utility::string_t m_name; + std::weak_ptr<::odata::edm::edm_named_type> m_target_type; + std::weak_ptr<::odata::edm::edm_navigation_source> m_target_navigation_source; +}; + +class odata_select_item +{ +public: + odata_select_item(std::shared_ptr<::odata::core::odata_query_node> end) + : m_end(end) {} + ~odata_select_item() {} + + static std::shared_ptr<::odata::core::odata_select_item> create( + std::shared_ptr<::odata::core::odata_query_node> end); + + std::shared_ptr<::odata::core::odata_query_node> end() const + { return m_end; } + +private: + odata_select_item(const odata_select_item &); + odata_select_item &operator=(const odata_select_item &); + +private: + std::shared_ptr<::odata::core::odata_query_node> m_end; +}; + +class odata_expand_item +{ +public: + odata_expand_item( + std::shared_ptr<::odata::core::odata_query_node> end, + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause, + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause, + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause, + std::shared_ptr<::odata::core::odata_search_clause> search_clause, + ::odata::common::nullable top, + ::odata::common::nullable skip, + ::odata::common::nullable count) + : m_end(end), + m_select_expand_clause(select_expand_clause), + m_filter_clause(filter_clause), + m_orderby_clause(orderby_clause), + m_search_clause(search_clause), + m_top(top), + m_skip(skip), + m_count(count) {} + ~odata_expand_item() {} + + static std::shared_ptr<::odata::core::odata_expand_item> create( + std::shared_ptr<::odata::core::odata_query_node> end); + + static std::shared_ptr<::odata::core::odata_expand_item> create( + std::shared_ptr<::odata::core::odata_query_node> end, + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause, + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause, + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause, + std::shared_ptr<::odata::core::odata_search_clause> search_clause, + ::odata::common::nullable top, + ::odata::common::nullable skip, + ::odata::common::nullable count); + + std::shared_ptr<::odata::core::odata_query_node> end() const + { return m_end; } + + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause() const + { return m_select_expand_clause; } + + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause() const + { return m_filter_clause; } + + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause() const + { return m_orderby_clause; } + + std::shared_ptr<::odata::core::odata_search_clause> search_clause() const + { return m_search_clause; } + + ::odata::common::nullable top() const + { return m_top; } + + ::odata::common::nullable skip() const + { return m_skip; } + + ::odata::common::nullable count() const + { return m_count; } + +private: + odata_expand_item(const odata_expand_item &); + odata_expand_item &operator=(const odata_expand_item &); + +private: + std::shared_ptr<::odata::core::odata_query_node> m_end; + std::shared_ptr<::odata::core::odata_select_expand_clause> m_select_expand_clause; + std::shared_ptr<::odata::core::odata_filter_clause> m_filter_clause; + std::shared_ptr<::odata::core::odata_orderby_clause> m_orderby_clause; + std::shared_ptr<::odata::core::odata_search_clause> m_search_clause; + ::odata::common::nullable m_top; + ::odata::common::nullable m_skip; + ::odata::common::nullable m_count; +}; + +class odata_query_option_parser +{ +public: + odata_query_option_parser( + std::shared_ptr<::odata::edm::edm_model> model, + std::shared_ptr<::odata::edm::edm_named_type> target_type, + std::shared_ptr<::odata::edm::edm_navigation_source> target_navigation_source) + : m_model(model), + m_target_type(target_type), + m_target_navigation_source(target_navigation_source) {} + ~odata_query_option_parser() {} + + std::shared_ptr<::odata::edm::edm_named_type> target_type() const { return m_target_type; } + + std::shared_ptr<::odata::edm::edm_navigation_source> target_navigation_source() const + { return m_target_navigation_source.lock(); } + + std::shared_ptr<::odata::core::odata_select_expand_clause> parse_select_and_expand( + const ::odata::utility::string_t& select_query, + const ::odata::utility::string_t& expand_query); + std::shared_ptr<::odata::core::odata_filter_clause> parse_filter(const ::odata::utility::string_t& filter_query); + std::shared_ptr<::odata::core::odata_orderby_clause> parse_orderby(const ::odata::utility::string_t& orderby_query); + std::shared_ptr<::odata::core::odata_search_clause> parse_search(const ::odata::utility::string_t& search_query); + ::odata::common::nullable parse_top(const ::odata::utility::string_t& top_query); + ::odata::common::nullable parse_skip(const ::odata::utility::string_t& skip_query); + ::odata::common::nullable parse_count(const ::odata::utility::string_t& count_query); + +private: + odata_query_option_parser(const odata_query_option_parser &); + odata_query_option_parser &operator=(const odata_query_option_parser &); + + std::shared_ptr<::odata::edm::edm_model> model() const { return m_model.lock(); } + std::shared_ptr create_implicit_range_variable() const; + + std::vector> parse_select(const ::odata::utility::string_t& select_query); + std::vector> parse_expand(const ::odata::utility::string_t& expand_query); + +private: + std::weak_ptr<::odata::edm::edm_model> m_model; + std::shared_ptr<::odata::edm::edm_named_type> m_target_type; + std::weak_ptr<::odata::edm::edm_navigation_source> m_target_navigation_source; +}; + +namespace odata_expression_token_kind { +enum { + None, + End, + Equal, + Identifier, + NullLiteral, + BooleanLiteral, + StringLiteral, + Int32Literal, + Int64Literal, + SingleLiteral, + DateTimeOffsetLiteral, + DurationLiteral, + DecimalLiteral, + DoubleLiteral, + GuidLiteral, + BinaryLiteral, + LeftParen, + RightParen, + Comma, + Colon, + Minus, + Slash, + Dot, + Star, + ParameterAlias, + JsonObjectOrArray, + QuotedLiteral +}; +} + +class odata_expression_token +{ +public: + odata_expression_token(int token_kind, const ::odata::utility::string_t &text, ::size_t pos) + : m_token_kind(token_kind), + m_text(text), + m_pos(pos) {} + ~odata_expression_token() {} + + ODATACPP_API static std::shared_ptr create_token( + int token_kind, + const ::odata::utility::string_t &text, + ::size_t pos); + + int token_kind() const { return m_token_kind; } + const ::odata::utility::string_t &text() const { return m_text; } + ::size_t position() const { return m_pos; } + + bool is_literal() const; + bool is_identifier() const; + bool is_identifier(const ::odata::utility::string_t &text) const; + + ODATACPP_API std::shared_ptr<::odata::core::odata_primitive_value> to_primitive_value() const; + +private: + odata_expression_token(const odata_expression_token &); + odata_expression_token &operator=(const odata_expression_token &); + +private: + int m_token_kind; + ::odata::utility::string_t m_text; + ::size_t m_pos; +}; + +class odata_expression_lexer +{ +public: + odata_expression_lexer(const ::odata::utility::string_t &expression) + : m_expression(expression), + m_pos(0) {} + ~odata_expression_lexer() {} + + ODATACPP_API static std::shared_ptr create_lexer(const ::odata::utility::string_t &expression); + + ODATACPP_API void next_token(); + ODATACPP_API std::shared_ptr peek_token(); + ODATACPP_API std::shared_ptr current_token(); + ODATACPP_API std::shared_ptr eat_token( + int token_kind, + const ::odata::utility::string_t &_expected_token); + + ODATACPP_API ::odata::utility::string_t eat_identifier(); + ODATACPP_API ::odata::utility::string_t eat_dotted_identifier(); + ODATACPP_API std::shared_ptr<::odata::core::odata_primitive_value> eat_literal(); + + ODATACPP_API ::odata::utility::string_t advance_through_balanced_parenthesis(); + +private: + odata_expression_lexer(const odata_expression_lexer &); + odata_expression_lexer &operator=(const odata_expression_lexer &); + + ::odata::utility::char_t char_at(::size_t i) const { return i < m_expression.size() ? m_expression[i] : 0; } + ::odata::utility::char_t current_char() const { return char_at(m_pos); } + ::odata::utility::char_t peek_char() const { return char_at(m_pos + 1); } + void next_char(); + ::odata::utility::char_t eat_char( + std::function validator, + const ::odata::utility::string_t &_expected_char); + + bool is_valid_start_of_identifier() const + { return ::odata::utility::is_letter(current_char()) || current_char() == U('_') || current_char() == U('$'); } + + ::odata::utility::string_t extract_text(::size_t start) const { return m_expression.substr(start, m_pos - start); } + + void scan_whitespaces(); + ::size_t scan_digits(); + ::size_t scan_hex_digits(); + void scan_identifier(); + void scan_json_object_or_array(); + void scan_string(); + int scan_number(); + + bool try_scan_datetimeoffset(::size_t start); + bool try_scan_guid(::size_t start); + int try_scan_quoted_literal(::size_t start); + + int determine_best_int_kind(::size_t start) const; + int determine_best_decimal_kind(::size_t start, bool can_be_decimal) const; + +private: + ::odata::utility::string_t m_expression; + ::size_t m_pos; + std::shared_ptr m_token; +}; + +namespace odata_expression_parser_subject { +enum { + SelectClause, + ExpandClause, + FilterClause, + OrderByClause, + SearchClause +}; +} + +class odata_expression_parser +{ +public: + odata_expression_parser(std::shared_ptr lexer, int parser_subject) + : m_lexer(lexer), + m_parser_subject(parser_subject) {} + ~odata_expression_parser() {} + + ODATACPP_API static std::shared_ptr<::odata::core::odata_query_node> parse_expression( + const ::odata::utility::string_t &expression, int parser_subject = odata_expression_parser_subject::FilterClause); + + ODATACPP_API static std::shared_ptr<::odata::core::odata_query_node> parse_expression( + std::shared_ptr lexer, int parser_subject = odata_expression_parser_subject::FilterClause); + +private: + odata_expression_parser(const odata_expression_parser &); + odata_expression_parser &operator=(const odata_expression_parser &); + + std::shared_ptr lexer() const { return m_lexer.lock(); } + + std::shared_ptr<::odata::core::odata_query_node> parse_logical_or(); + std::shared_ptr<::odata::core::odata_query_node> parse_logical_and(); + std::shared_ptr<::odata::core::odata_query_node> parse_comparison(); + std::shared_ptr<::odata::core::odata_query_node> parse_additive(); + std::shared_ptr<::odata::core::odata_query_node> parse_multiplicative(); + std::shared_ptr<::odata::core::odata_query_node> parse_unary(); + std::shared_ptr<::odata::core::odata_query_node> parse_postfix(); + std::shared_ptr<::odata::core::odata_query_node> parse_primary(); + + std::shared_ptr<::odata::core::odata_query_node> parse_parameter_alias(); + std::shared_ptr<::odata::core::odata_query_node> parse_parenthesis(); + std::shared_ptr<::odata::core::odata_query_node> parse_literal(); + std::shared_ptr<::odata::core::odata_query_node> parse_lambda( + std::shared_ptr<::odata::core::odata_query_node> parent); + std::shared_ptr<::odata::core::odata_query_node> parse_identifier( + std::shared_ptr<::odata::core::odata_query_node> parent); + std::shared_ptr<::odata::core::odata_query_node> expand_to_function_call( + const ::odata::utility::string_t &identifier, + std::shared_ptr<::odata::core::odata_query_node> parent); + std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_query_node>> parse_named_value(); + std::shared_ptr<::odata::core::odata_query_node> parse_star( + std::shared_ptr<::odata::core::odata_query_node> parent); + +private: + std::weak_ptr m_lexer; + std::unordered_set<::odata::utility::string_t> m_lambda_parameters; + int m_parser_subject; +}; + +class odata_uri_parser +{ +public: + odata_uri_parser(std::shared_ptr<::odata::edm::edm_model> model) + : m_model(model) {} + ~odata_uri_parser() {} + + ODATACPP_API std::shared_ptr<::odata::core::odata_path> parse_path(const ::odata::utility::string_t& path); + ODATACPP_API std::shared_ptr<::odata::core::odata_select_expand_clause> parse_select_and_expand( + const ::odata::utility::string_t& select_query, + const ::odata::utility::string_t& expand_query); + ODATACPP_API std::shared_ptr<::odata::core::odata_filter_clause> parse_filter( + const ::odata::utility::string_t& filter_query); + ODATACPP_API std::shared_ptr<::odata::core::odata_orderby_clause> parse_orderby( + const ::odata::utility::string_t& orderby_query); + ODATACPP_API std::shared_ptr<::odata::core::odata_search_clause> parse_search( + const ::odata::utility::string_t& search_query); + ODATACPP_API ::odata::common::nullable parse_top(const ::odata::utility::string_t& top_query); + ODATACPP_API ::odata::common::nullable parse_skip(const ::odata::utility::string_t& skip_query); + ODATACPP_API ::odata::common::nullable parse_count(const ::odata::utility::string_t& count_query); + ODATACPP_API std::shared_ptr<::odata::core::odata_uri> parse_uri(const ::odata::utility::uri &uri); + + std::shared_ptr<::odata::edm::edm_model> model() const { return m_model.lock(); } + + static std::map<::odata::utility::string_t, ::odata::utility::string_t> split_query_options(const ::odata::utility::string_t &query, bool is_semicolon_separated = false); + +private: + odata_uri_parser(const odata_uri_parser &); + odata_uri_parser &operator=(const odata_uri_parser &); + + std::shared_ptr path_parser(); + std::shared_ptr query_option_parser(); + + static ::size_t advance_through_string_literal(const ::odata::utility::string_t &query, ::size_t start); + static ::size_t advance_through_balanced_parenthesis(const ::odata::utility::string_t &query, ::size_t start); + +private: + std::weak_ptr<::odata::edm::edm_model> m_model; + std::shared_ptr m_path_parser; + std::shared_ptr m_query_option_parser; +}; + +}} \ No newline at end of file diff --git a/include/odata/core/odata_value.h b/include/odata/core/odata_value.h new file mode 100644 index 0000000..77d2bc0 --- /dev/null +++ b/include/odata/core/odata_value.h @@ -0,0 +1,128 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/core/odata_property_map.h" +#include "odata/core/odata_operation.h" + +namespace odata { namespace core +{ + +class odata_entity_value; +// A combination of the property type (see enum above) and a string representation of the property value. +class odata_value +{ +public: + /// Default constructor + odata_value() : m_property_type(std::make_shared<::odata::edm::edm_named_type>()), m_is_null_value(), m_is_top_level(){} + + odata_value(std::shared_ptr<::odata::edm::edm_named_type>type, bool is_null_value = false) + : m_property_type(type), m_is_null_value(is_null_value), m_is_top_level() + { + } + + virtual ~odata_value(){}; + + std::shared_ptr<::odata::edm::edm_named_type> get_value_type() const { return m_property_type; } + + void set_value_type(std::shared_ptr<::odata::edm::edm_named_type> property_type) + { + m_property_type = property_type; + } + + void set_is_top_level(bool is_top_level) + { + m_is_top_level = is_top_level; + } + + void set_instance_annotation(::odata::utility::string_t name, std::shared_ptr value) + { + m_instance_annotations[name] = value; + } + + bool try_get_instance_annotation_value(::odata::utility::string_t name, std::shared_ptr& value) + { + const auto& annotation = m_instance_annotations.find(name); + bool found = (annotation != m_instance_annotations.end()); + + if (found) + { + value = annotation->second; + } + + return found; + } + + std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_instance_annotations() + { + return m_instance_annotations; + } + + void set_context_url(::odata::utility::uri value) + { + m_context_url = value; + } + + ::odata::utility::uri get_context_url() + { + return m_context_url; + } + + bool is_top_level() + { + return m_is_top_level; + } + + std::vector>& get_actions() + { + return m_actions; + } + + std::vector>& get_functions() + { + return m_functions; + } + + void add_action(std::shared_ptr action) + { + m_actions.push_back(action); + } + + void add_function(std::shared_ptr function) + { + m_functions.push_back(function); + } + + void set_type_name(::odata::utility::string_t type_name) + { + m_type_name = type_name; + } + + ::odata::utility::string_t get_type_name() + { + return m_type_name; + } + +private: + friend class entity; + friend class odata_property_map; + + std::shared_ptr<::odata::edm::edm_named_type> m_property_type; + bool m_is_null_value; + bool m_is_top_level; + + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_instance_annotations; + + ::odata::utility::uri m_context_url; + ::odata::utility::string_t m_type_name; + + std::vector> m_actions; + std::vector> m_functions; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_entity_container.h b/include/odata/edm/edm_entity_container.h new file mode 100644 index 0000000..3a2647e --- /dev/null +++ b/include/odata/edm/edm_entity_container.h @@ -0,0 +1,173 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_entity_set.h" +#include "odata/edm/edm_singleton.h" +#include "odata/edm/edm_operation_import.h" + +namespace odata { namespace edm +{ + +/// +/// Represents a container of entities +/// +class edm_entity_container +{ +public: + /// + /// Constructor + /// + /// The name of the entity container. + edm_entity_container(::odata::utility::string_t name, bool is_default) : + m_name(name), m_is_default(is_default) + { + } + + ~edm_entity_container() + { +#ifdef _MS_WINDOWS_DEBUG + std::wcout << U("destroy edm_entity_container") << std::endl; +#endif + } + + /// + /// Gets the name of the entity container + /// + /// The name of the entity container. + const ::odata::utility::string_t& get_name() const + { + return m_name; + } + + /// + /// Gets the is_default_container property + /// + /// true if this is the default container of the service, false otherwise + bool is_default_container() const + { + return m_is_default; + } + + /// + /// + /// + void add_entity_set(std::shared_ptr et) + { + if (et) + { + m_entity_sets[et->get_name()] = et; + m_entity_set_vector.push_back(et); + } + } + + /// + /// + /// + void add_singleton(std::shared_ptr sg) + { + if (sg) + { + m_singletons[sg->get_name()] = sg; + m_singleton_vector.push_back(sg); + } + } + + /// + /// + /// + const std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_operation_imports() const + { + return m_operation_imports; + } + + const std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_singletons() const + { + return m_singletons; + } + + const std::vector>& get_operation_import_vector() const + { + return m_operation_import_vector; + } + + const std::vector>& get_singleton_vector() const + { + return m_singleton_vector; + } + + const std::vector>& get_entity_set_vector() const + { + return m_entity_set_vector; + } + + /// + /// + /// + void add_operation_import(std::shared_ptr op) + { + if (op) + { + m_operation_imports[op->get_name()] = op; + m_operation_import_vector.push_back(op); + } + } + + /// + /// Gets the beginning iterator of the properties of the type + /// + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::const_iterator begin() const + { + return m_entity_sets.cbegin(); + } + + /// + /// Gets the end iterator of the properties of the type + /// + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::const_iterator end() const + { + return m_entity_sets.cend(); + } + + /// + /// Looks up an entity set of the container by name. + /// + /// The qualified or unqualified name of the entity set. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_entity_set(::odata::utility::string_t name) const; + + /// + /// Looks up a singleton of the container by name. + /// + /// The qualified or unqualified name of the singleton. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_singleton(::odata::utility::string_t name) const; + + /// + /// Looks up an operation import of the container by name. + /// + /// The qualified or unqualified name of the operation import. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_operation_import(::odata::utility::string_t name) const; + +private: + friend class edm_schema; + + bool m_is_default; + ::odata::utility::string_t m_name; + ::odata::utility::string_t m_extends; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_entity_sets; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_singletons; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_operation_imports; + std::vector> m_entity_set_vector; + std::vector> m_singleton_vector; + std::vector> m_operation_import_vector; + +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_entity_set.h b/include/odata/edm/edm_entity_set.h new file mode 100644 index 0000000..88ba573 --- /dev/null +++ b/include/odata/edm/edm_entity_set.h @@ -0,0 +1,68 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_navigation_source.h" +#include "odata/edm/edm_type.h" + +namespace odata { namespace edm +{ + +class edm_entity_container; + +/// +/// Represents a set of entities in a container +/// +class edm_entity_set : public edm_navigation_source +{ +public: + /// + /// Constructor + /// + /// The name of the entity set. + edm_entity_set(::odata::utility::string_t name, ::odata::utility::string_t type) + : m_type_name(type), edm_navigation_source(name, container_resource_type::E_RESOURCE_ENTITY_SET) + {} + + edm_entity_set(::odata::utility::string_t name, std::shared_ptr type) + : m_entity_type(type), edm_navigation_source(name, container_resource_type::E_RESOURCE_ENTITY_SET) + {} + + ~edm_entity_set() + { +#ifdef _MS_WINDOWS_DEBUG + std::wcout << U("destroy edm_entity_set") << std::endl; +#endif + } + + /// + /// Gets the name of the type of the entity set + /// + /// The name of the type of the entity set. + const ::odata::utility::string_t& get_entity_type_name() const + { + return m_type_name; + } + + std::shared_ptr get_entity_type() const + { + return m_entity_type.lock(); + } + + void set_entity_type(const std::shared_ptr& entity_type) + { + m_entity_type = entity_type; + } + +private: + friend class edm_entity_container; + ::odata::utility::string_t m_type_name; + std::weak_ptr m_entity_type; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_model.h b/include/odata/edm/edm_model.h new file mode 100644 index 0000000..689d324 --- /dev/null +++ b/include/odata/edm/edm_model.h @@ -0,0 +1,100 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_schema.h" + +namespace odata { namespace edm +{ + +class edm_model +{ +public: + +#ifdef _MS_WINDOWS_DEBUG + ~edm_model() + { + std::wcout << U("destroy model") << std::endl; + } +#endif + + std::shared_ptr operator[](const ::odata::utility::string_t& name) + { + for (size_t i = 0; i < m_schemata.size(); ++i) + { + if (m_schemata[i]->get_name() == name) + { + return m_schemata[i]; + } + } + return nullptr; + } + + std::shared_ptr add_schema(const ::odata::utility::string_t& name, const ::odata::utility::string_t& alias) + { + auto val = std::make_shared(name, alias); + m_schemata.push_back(val); + return val; + } + + const std::vector>& get_schema() + { + return m_schemata; + } + + const ::odata::utility::string_t& get_version() const + { + return m_version; + } + + void set_version(const ::odata::utility::string_t& version) + { + m_version = version; + } + + /// + /// Looks up an entity type of the schema by name. + /// + /// The qualified or unqualified name of the entity type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_entity_type(::odata::utility::string_t name) const; + + /// + /// Looks up a complex type of the schema by name. + /// + /// The qualified or unqualified name of the entity type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_complex_type(::odata::utility::string_t name) const; + + /// + /// Looks up an enum type of the schema by name. + /// + /// The qualified or unqualified name of the enum type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_enum_type(::odata::utility::string_t name) const; + + /// + /// Looks up an operation type of the schema by name. + /// + /// The qualified or unqualified name of the operation type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_operation_type(::odata::utility::string_t name) const; + + /// + /// Looks up an entity container of the schema by name. + /// + /// The qualified or unqualified name of the entity container; an empty string refers to the default container + /// A pointer to the container if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_container(::odata::utility::string_t name = U("")) const; + +private: + std::vector> m_schemata; + ::odata::utility::string_t m_version; +}; + +}} diff --git a/include/odata/edm/edm_model_reader.h b/include/odata/edm/edm_model_reader.h new file mode 100644 index 0000000..3e81db0 --- /dev/null +++ b/include/odata/edm/edm_model_reader.h @@ -0,0 +1,58 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/common/xmlhelpers.h" + +namespace odata { namespace edm +{ + +class edm_model_reader : public ::odata::edm::xml_reader +{ +public: + edm_model_reader(std::istream& stream) : + xml_reader(stream), m_parsing_key(false), m_model(std::make_shared()), + m_current_st(nullptr), m_current_enum(nullptr), m_current_operation(nullptr) + { + } + + + std::shared_ptr get_model() + { + return m_model; + } + + ODATACPP_API bool parse(); + +protected: + ODATACPP_API virtual void handle_begin_element(const ::odata::utility::string_t& elementName); + ODATACPP_API virtual void handle_end_element(const ::odata::utility::string_t& elementName); + ODATACPP_API virtual void handle_element(const ::odata::utility::string_t& elementName); + + +private: + void _process_property(); + void _process_navigation_property(); + void _process_operation_parameter(); + void _process_operation_return_type(); + void _process_navigation_property_binding(); + + bool m_parsing_key; + + // These variables are used to cache values for each entity + std::shared_ptr m_model; + std::shared_ptr m_current_schema; + std::shared_ptr m_current_container; + std::shared_ptr m_current_entity_set; + std::shared_ptr m_current_singleton; + edm_structured_type* m_current_st; + edm_enum_type* m_current_enum; + edm_operation_type* m_current_operation; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_model_utility.h b/include/odata/edm/edm_model_utility.h new file mode 100644 index 0000000..6375ace --- /dev/null +++ b/include/odata/edm/edm_model_utility.h @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" + +namespace odata { namespace edm +{ + +class edm_model_utility +{ +public: + ODATACPP_API static std::shared_ptr get_edm_type_from_name(const ::odata::utility::string_t& type_name); + ODATACPP_API static ::odata::utility::string_t get_strong_type_name_from_edm_type_name(const std::shared_ptr& primitive_type); + ODATACPP_API static ::odata::utility::string_t get_strong_type_default_value_from_edm_type_name(const std::shared_ptr& primitive_type); + ODATACPP_API static std::shared_ptr get_edm_primitive_type_from_name(const ::odata::utility::string_t& type_name); + ODATACPP_API static ::odata::utility::string_t get_type_kind_name_from_edm_type(const std::shared_ptr& ptype); + ODATACPP_API static bool get_primitive_kind_from_edm_type(const std::shared_ptr& edm_type, edm_primitive_type_kind_t& primitive_kind); + ODATACPP_API static void resolve_edm_types_after_parsing(const std::shared_ptr& model); + ODATACPP_API static std::shared_ptr<::odata::edm::edm_named_type> get_property_type_from_name(const std::shared_ptr<::odata::edm::edm_entity_type>& entity_type, const ::odata::utility::string_t& property_name); + + ODATACPP_API static std::shared_ptr get_collection_element_type(const std::shared_ptr& input_type); + ODATACPP_API static std::shared_ptr get_navigation_element_type(const std::shared_ptr& input_type); + ODATACPP_API static std::shared_ptr<::odata::edm::edm_entity_type> get_entity_type(std::shared_ptr<::odata::edm::edm_named_type> type); + ODATACPP_API static std::shared_ptr<::odata::edm::edm_structured_type> get_structured_type(std::shared_ptr<::odata::edm::edm_named_type> type); + ODATACPP_API static std::shared_ptr<::odata::edm::edm_named_type> get_navigation_source_type(std::shared_ptr<::odata::edm::edm_navigation_source> navigation_source); + + ODATACPP_API static bool is_collection_of_entity(std::shared_ptr<::odata::edm::edm_named_type> type); + +private: + static std::shared_ptr resolve_type_from_name(const std::shared_ptr& model, ::odata::utility::string_t qualified_name); + static void resolve_type_under_structured_type(const std::shared_ptr& model, const std::shared_ptr& structyred_type); + static void resolve_type_under_operation_type(const std::shared_ptr& model, const std::shared_ptr& operationType); + static std::shared_ptr resolve_undetermined_type(const std::shared_ptr& model, const std::shared_ptr& undeterminedType); + static void resolve_type_under_entity_container(const std::shared_ptr& model, const std::shared_ptr& entity_container); + static void resovle_entity_base_type(const std::shared_ptr& model, const std::shared_ptr& entity_type); + static void resovle_complex_base_type(const std::shared_ptr& model, const std::shared_ptr& complex_type); + static void resolve_navigation_path_for_non_contained_navigation(const std::shared_ptr& model, const std::shared_ptr& entity_container); + static ::odata::utility::string_t get_collection_element_name(const ::odata::utility::string_t& collection_full_name); + static std::shared_ptr get_navigation_property_from_path(const std::shared_ptr& model, const std::shared_ptr& entity_type, const ::odata::utility::string_t& navigation_path); +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_model_writer.h b/include/odata/edm/edm_model_writer.h new file mode 100644 index 0000000..25c1102 --- /dev/null +++ b/include/odata/edm/edm_model_writer.h @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/odata_edm.h" +#include "odata/common/xmlhelpers.h" + +namespace odata { namespace edm +{ + +class edm_model_writer : public ::odata::edm::xml_writer +{ +public: + edm_model_writer(std::ostream& stream) + { + initialize(stream); + } + + ODATACPP_API void write_model(std::shared_ptr model); + +protected: + ODATACPP_API void write_schema(std::shared_ptr schema); + + ODATACPP_API void write_enum_type(std::shared_ptr enum_type); + + ODATACPP_API void write_complex_type(std::shared_ptr complex_type); + + ODATACPP_API void write_entity_type(std::shared_ptr entity_type); + + ODATACPP_API void write_operation(std::shared_ptr operation); + + ODATACPP_API void write_entity_container(std::shared_ptr entity_container); + + ODATACPP_API void write_entity_set(std::shared_ptr entity_set); + + ODATACPP_API void write_singleton(std::shared_ptr singleton); + + ODATACPP_API void write_operation_import(std::shared_ptr operation_import); + +private: +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_navigation_source.h b/include/odata/edm/edm_navigation_source.h new file mode 100644 index 0000000..e76b1dc --- /dev/null +++ b/include/odata/edm/edm_navigation_source.h @@ -0,0 +1,62 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_type.h" + +namespace odata { namespace edm +{ + +enum container_resource_type +{ + E_RESOURCE_ENTITY_SET = 0, + E_RESOURCE_SINGLETON, +}; + +class edm_navigation_source +{ +public: + edm_navigation_source(const ::odata::utility::string_t& name, container_resource_type resource_type) : m_name(name), m_resource_type(resource_type) + {} + + virtual ~edm_navigation_source() + {} + + void add_navigation_source(const ::odata::utility::string_t& property_path_name, const ::odata::utility::string_t& target_name) + { + navigation_property_mapping[property_path_name] = target_name; + } + + ::odata::utility::string_t get_navigation_source_name(const ::odata::utility::string_t& property_path_name) + { + return navigation_property_mapping[property_path_name]; + } + + const std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>& get_navigation_sources() const + { + return navigation_property_mapping; + } + + + const ::odata::utility::string_t& get_name() const + { + return m_name; + } + + container_resource_type get_resource_type() const + { + return m_resource_type; + } + +protected: + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> navigation_property_mapping; + ::odata::utility::string_t m_name; + container_resource_type m_resource_type; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_operation_import.h b/include/odata/edm/edm_operation_import.h new file mode 100644 index 0000000..d4935bc --- /dev/null +++ b/include/odata/edm/edm_operation_import.h @@ -0,0 +1,74 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_type.h" + +namespace odata { namespace edm +{ + +class edm_entity_container; + +/// +/// +/// +enum OperationImportKind +{ + ActionImport = 0, + FunctionImport +}; + +/// +/// Represents a set of operation imports in a container +/// +class edm_operation_import +{ +public: + edm_operation_import(::odata::utility::string_t name, ::odata::utility::string_t operation_name, ::odata::utility::string_t entity_set_path, bool is_in_service_document, OperationImportKind operation_import_kind) : + m_name(name), m_operation_name(operation_name), m_entity_set_path(entity_set_path), m_is_in_service_document(is_in_service_document), m_operation_import_kind(operation_import_kind) + { + m_operation_type = nullptr; + } + + const ::odata::utility::string_t get_name() const + { + return m_name; + } + + const std::shared_ptr get_operation_type() const + { + return m_operation_type; + } + + const OperationImportKind get_operation_import_kind() const + { + return m_operation_import_kind; + } + + const ::odata::utility::string_t get_entity_set_name() const + { + return m_entity_set_path; + } + + void set_operation_type(std::shared_ptr operationType) + { + m_operation_type = operationType; + } + +private: + friend class edm_entity_container; + + ::odata::utility::string_t m_name; + ::odata::utility::string_t m_operation_name; + std::shared_ptr m_operation_type; + ::odata::utility::string_t m_entity_set_path; + bool m_is_in_service_document; + OperationImportKind m_operation_import_kind; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_schema.h b/include/odata/edm/edm_schema.h new file mode 100644 index 0000000..9df55a7 --- /dev/null +++ b/include/odata/edm/edm_schema.h @@ -0,0 +1,204 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_entity_container.h" + +namespace odata { namespace edm +{ + +class edm_model_utility; + +/// +/// +/// +class edm_schema +{ +public: + /// + /// + /// + edm_schema() + { + } + + /// + /// + /// + edm_schema(::odata::utility::string_t namesp, ::odata::utility::string_t alias) : + m_namespace(namesp), m_alias(alias) + { + } + +#ifdef _MS_WINDOWS_DEBUG + ~edm_schema() + { + std::wcout << U("destroy edm_schema") << std::endl; + } +#endif + + /// + /// Adds an entity type to the schema + /// + void add_entity_type(std::shared_ptr et) + { + m_entity_types[et->get_name()] = et; + } + + /// + /// Adds an complex type to the schema + /// + void add_complex_type(std::shared_ptr et) + { + m_complex_types[et->get_name()] = et; + } + + /// + /// + /// + void add_enum_type(std::shared_ptr et) + { + m_enum_types[et->get_name()] = et; + } + + /// + /// + /// + void add_operation_type(std::shared_ptr et) + { + m_operation_types[et->get_name()] = et; + } + + /// + /// + /// + void add_container(std::shared_ptr et) + { + m_entity_containers[et->get_name()] = et; + } + + /// + /// Looks up an entity type of the schema by name. + /// + /// The qualified or unqualified name of the entity type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_entity_type(::odata::utility::string_t name) const; + + /// + /// Looks up a complex type of the schema by name. + /// + /// The qualified or unqualified name of the complex type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_complex_type(::odata::utility::string_t name) const; + + /// + /// Looks up an enum type of the schema by name. + /// + /// The qualified or unqualified name of the enum type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_enum_type(::odata::utility::string_t name) const; + + /// + /// Looks up an operation type of the schema by name. + /// + /// The qualified or unqualified name of the entity type. + /// A pointer to the type if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_operation_type(::odata::utility::string_t name) const; + + /// + /// Looks up an entity container of the schema by name. + /// + /// The qualified or unqualified name of the entity container; an empty string refers to the default container + /// A pointer to the container if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_container(::odata::utility::string_t name = U("")) const; + + const std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_entity_types() const + { + return m_entity_types; + } + + const std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_complex_types() const + { + return m_complex_types; + } + + const std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_enum_types() const + { + return m_enum_types; + } + + const std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_operation_types() const + { + return m_operation_types; + } + + const std::unordered_map<::odata::utility::string_t, std::shared_ptr>& get_containers() const + { + return m_entity_containers; + } + + std::vector> get_entity_types_vector() const + { + std::vector> ret; + for (auto it = m_entity_types.cbegin(); it != m_entity_types.cend(); ++it) + ret.push_back(it->second); + return ret; + } + + std::vector> get_complex_types_vector() const + { + std::vector> ret; + for (auto it = m_complex_types.cbegin(); it != m_complex_types.cend(); ++it) + ret.push_back(it->second); + return ret; + } + + std::vector> get_enum_types_vector() const + { + std::vector> ret; + for (auto it = m_enum_types.cbegin(); it != m_enum_types.cend(); ++it) + ret.push_back(it->second); + return ret; + } + + std::vector> get_operation_types_vector() const + { + std::vector> ret; + for (auto it = m_operation_types.cbegin(); it != m_operation_types.cend(); ++it) + ret.push_back(it->second); + return ret; + } + + std::vector> get_containers_vector() const + { + std::vector> ret; + for (auto it = m_entity_containers.cbegin(); it != m_entity_containers.cend(); ++it) + ret.push_back(it->second); + return ret; + } + + const ::odata::utility::string_t& get_name() const + { + return m_namespace; + } + +private: + friend class edm_model_utility; + + ::odata::utility::string_t m_namespace; + ::odata::utility::string_t m_alias; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_entity_types; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_complex_types; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_enum_types; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_operation_types; + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_entity_containers; + + std::vector> m_collection_navigation_types; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_singleton.h b/include/odata/edm/edm_singleton.h new file mode 100644 index 0000000..6d58235 --- /dev/null +++ b/include/odata/edm/edm_singleton.h @@ -0,0 +1,69 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_type.h" + +namespace odata { namespace edm +{ + +class edm_entity_container; + +/// +/// Represents a set of entities in a container +/// +class edm_singleton : public edm_navigation_source +{ +public: + /// + /// Constructor + /// + /// The name of the entity set. + edm_singleton(::odata::utility::string_t name, ::odata::utility::string_t type) + : m_type_name(type), edm_navigation_source(name, container_resource_type::E_RESOURCE_SINGLETON) + {} + + edm_singleton(::odata::utility::string_t name, std::shared_ptr type) + : m_entity_type(type), edm_navigation_source(name, container_resource_type::E_RESOURCE_SINGLETON) + {} + + /// + /// Gets the name of the entity set + /// + /// The name of the entity set. + const ::odata::utility::string_t& get_name() const + { + return m_name; + } + + /// + /// Gets the name of the type of the entity set + /// + /// The name of the type of the entity set. + const ::odata::utility::string_t& get_entity_type_name() const + { + return m_type_name; + } + + std::shared_ptr get_entity_type() const + { + return m_entity_type.lock(); + } + + void set_entity_type(const std::shared_ptr& entity_type) + { + m_entity_type = entity_type; + } + +private: + friend class edm_entity_container; + ::odata::utility::string_t m_type_name; + std::weak_ptr m_entity_type; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/edm_type.h b/include/odata/edm/edm_type.h new file mode 100644 index 0000000..7129218 --- /dev/null +++ b/include/odata/edm/edm_type.h @@ -0,0 +1,766 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" + +namespace odata { namespace edm +{ + +#define undefined_value 0xffffffff +#define PAYLOAD_ANNOTATION_NAVIGATIONLINK U("@odata.navigationLink") +#define PAYLOAD_ANNOTATION_READLINK U("@odata.readLink") +#define PAYLOAD_ANNOTATION_EDITLINK U("@odata.editLink") +#define PAYLOAD_ANNOTATION_TYPE U("@odata.type") +#define PAYLOAD_ANNOTATION_ID U("@odata.id") + +class edm_schema; +class edm_model_reader; + +/// +/// Defines EDM metatypes. +/// +enum edm_type_kind_t +{ + None, + Primitive, + Entity, + Complex, + Collection, + Enum, + Link, + Navigation, + Operation, + PayloadAnnotation, + Unknown, +}; + +/// +/// Enumerates the kinds of Edm Primitives. +/// +enum edm_primitive_type_kind_t +{ + NoneVal, + Binary, + Boolean, + Byte, + DateTimeOffset, + Duration, + Decimal, + Double, + Guid, + Int16, + Int32, + Int64, + SByte, + Single, + String, + Stream, +}; + +class edm_named_type +{ +public: + edm_named_type() : m_type_kind(edm_type_kind_t::None), m_name(U("")), m_namespace(U("")) + { +#ifdef _MS_WINDOWS_DEBUG + std::wcout << U("create :") << m_namespace << U(".") << m_name << std::endl; +#endif + } + + edm_named_type(edm_type_kind_t type_kind) : m_type_kind(type_kind), m_name(U("")), m_namespace(U("")) + { +#ifdef _MS_WINDOWS_DEBUG + std::wcout << U("create :") << m_namespace << U(".") << m_name << std::endl; +#endif + } + + edm_named_type(::odata::utility::string_t name, ::odata::utility::string_t name_space, edm_type_kind_t type_kind) + : m_type_kind(type_kind), m_name(name), m_namespace(name_space) + { +#ifdef _MS_WINDOWS_DEBUG + std::wcout << U("create :") << m_namespace << U(".") << m_name << std::endl; +#endif + } + + virtual ~edm_named_type() + { +#ifdef _MS_WINDOWS_DEBUG + std::wcout << U("destroy :") << m_namespace << U(".") << m_name << std::endl; +#endif + } + + const ::odata::utility::string_t& get_name() const + { + return m_name; + } + + ::odata::utility::string_t get_full_name() + { + if (m_namespace.empty()) + { + return m_name; + } + else + { + return m_namespace + U(".") + m_name; + } + } + + void set_name(::odata::utility::string_t name) + { + m_name = name; + } + + const ::odata::utility::string_t& get_namespace() const + { + return m_namespace; + } + + void set_namespace(::odata::utility::string_t name_space) + { + m_namespace = name_space; + } + + const edm_type_kind_t& get_type_kind() const + { + return m_type_kind; + } + + void set_type_kind(edm_type_kind_t kind) + { + m_type_kind = kind; + } + + ODATACPP_API static std::shared_ptr EDM_UNKNOWN(); + +protected: + static std::mutex &mutex(); + +protected: + friend class edm_schema; + + ::odata::utility::string_t m_name; + ::odata::utility::string_t m_namespace; + edm_type_kind_t m_type_kind; + + static std::shared_ptr _EDM_UNKNOWN; +}; + +class edm_primitive_type : public edm_named_type +{ +public: + const edm_primitive_type_kind_t get_primitive_kind() const + { + return m_primitive_kind; + } + + bool type_equal(std::shared_ptr type) const + { + if (!type) + { + return false; + } + + if (type->get_type_kind() == edm_type_kind_t::Primitive) + { + edm_primitive_type *p_primitive_type = (edm_primitive_type*)type.get(); + if (p_primitive_type && p_primitive_type->m_primitive_kind == m_primitive_kind) + { + return true; + } + } + + return false; + } + + ODATACPP_API static std::shared_ptr BINARY(); + ODATACPP_API static std::shared_ptr BOOLEAN(); + ODATACPP_API static std::shared_ptr BYTE(); + ODATACPP_API static std::shared_ptr DATETIMEOFFSET(); + ODATACPP_API static std::shared_ptr DURATION(); + ODATACPP_API static std::shared_ptr DECIMAL(); + ODATACPP_API static std::shared_ptr DOUBLE(); + ODATACPP_API static std::shared_ptr GUID(); + ODATACPP_API static std::shared_ptr INT16(); + ODATACPP_API static std::shared_ptr INT32(); + ODATACPP_API static std::shared_ptr INT64(); + ODATACPP_API static std::shared_ptr SBYTE(); + ODATACPP_API static std::shared_ptr SINGLE(); + ODATACPP_API static std::shared_ptr STRING(); + ODATACPP_API static std::shared_ptr STREAM(); + ODATACPP_API static std::shared_ptr UNKNOWN(); + +private: + edm_primitive_type(::odata::utility::string_t name, edm_primitive_type_kind_t primitive_kind) + : edm_named_type(name, U(""), edm_type_kind_t::Primitive), m_primitive_kind(primitive_kind) + { + } + + static std::mutex &mutex(); + + edm_primitive_type_kind_t m_primitive_kind; + + static std::shared_ptr _BINARY; + static std::shared_ptr _BOOLEAN; + static std::shared_ptr _BYTE; + static std::shared_ptr _DATETIMEOFFSET; + static std::shared_ptr _DURATION; + static std::shared_ptr _DECIMAL; + static std::shared_ptr _DOUBLE; + static std::shared_ptr _GUID; + static std::shared_ptr _INT16; + static std::shared_ptr _INT32; + static std::shared_ptr _INT64; + static std::shared_ptr _SBYTE; + static std::shared_ptr _SINGLE; + static std::shared_ptr _STRING; + static std::shared_ptr _STREAM; + static std::shared_ptr _UNKNOWN; + +}; + +/// +/// Represents a CSDL Property, used in ComplexType and EntityType definitions. +/// +class edm_property_type +{ +public: + /// + /// Constructor + /// + edm_property_type() : m_is_nullable(true), m_is_unicode(false), m_maxLength(undefined_value), m_scale(0), m_precision(undefined_value) + { + } + + edm_property_type(const ::odata::utility::string_t& name) : m_name(name), + m_is_nullable(true), m_is_unicode(false), m_maxLength(undefined_value), m_scale(0), m_precision(undefined_value) + { + } + + edm_property_type(const ::odata::utility::string_t& name, bool is_nullable, unsigned int max_length, bool is_unicode, unsigned int scale) : + m_name(name), m_is_nullable(is_nullable), m_is_unicode(is_unicode), m_maxLength(max_length), m_scale(scale), m_precision(undefined_value) + { + } + + edm_property_type(const ::odata::utility::string_t& name, bool is_nullable, std::shared_ptr type) : + m_name(name), m_is_nullable(is_nullable), m_type(type), m_is_unicode(false), m_maxLength(undefined_value), m_scale(0), m_precision(undefined_value) + { + } + + + /// + /// Gets the (unqualified) name of the property. + /// + /// The name of the property. + const ::odata::utility::string_t& get_name() const + { + return m_name; + } + + /// + /// Gets the nullable property of the property + /// + /// true if the property can be absent in a entity, false otherwise + bool is_nullable() const + { + return m_is_nullable; + } + + std::shared_ptr get_property_type() + { + return m_type; + } + + void set_property_type(std::shared_ptr type) + { + m_type = type; + } + + void set_precision(unsigned int precision) + { + m_precision = precision; + } + + /// + /// Gets the default value of the property, as represented in the CSDL. + /// + /// A string representing the default value of the property. + const ::odata::utility::string_t& default_value() const + { + return m_default; + } + +private: + friend class edm_structured_type; + + bool m_is_nullable; + bool m_is_unicode; + ::odata::utility::string_t m_name; + ::odata::utility::string_t m_default; + unsigned int m_maxLength; + unsigned int m_scale; + unsigned int m_precision; + + std::shared_ptr m_type; +}; + +class edm_collection_type : public edm_named_type +{ +public: + edm_collection_type(std::shared_ptr element_type) + : edm_named_type(U("Collection(") + element_type->get_full_name() + U(")"), element_type->get_namespace(), edm_type_kind_t::Collection), m_element_type(element_type) + { + } + + std::shared_ptr get_element_type() const + { + return m_element_type.lock(); + } + + void set_element_type(std::shared_ptr type) + { + m_element_type = type; + set_name(U("Collection(") + type->get_full_name() + U(")")); + set_namespace(type->get_namespace()); + } + +protected: + std::weak_ptr m_element_type; +}; + +class edm_payload_annotation_type : public edm_named_type +{ +public: + edm_payload_annotation_type(::odata::utility::string_t name) + : edm_named_type(name, U(""), edm_type_kind_t::PayloadAnnotation) + { + } +}; + +/// +/// Represents what is shared between edm_complex_type and edm_entity_type. +/// +class edm_structured_type : public edm_named_type +{ +public: + /// + /// Constructor + /// + /// The name of the type. + edm_structured_type(::odata::utility::string_t name, ::odata::utility::string_t name_space, edm_type_kind_t type_kind, ::odata::utility::string_t basetype, bool isAbstract, bool isOpenType) + : edm_named_type(name, name_space, type_kind) , m_baseTypeName(basetype), m_is_abstract(isAbstract), m_is_openType(isOpenType), m_base_type(nullptr) + { + } + + /// + /// Adds a property to the type. + /// + /// A pointer to the property to add. + void add_property(std::shared_ptr prop) + { + m_properties[prop->get_name()] = prop; + } + + /// + /// Gets the beginning iterator of the properties of the type + /// + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::const_iterator begin() const + { + return m_properties.cbegin(); + } + + /// + /// Gets the end iterator of the properties of the type + /// + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::const_iterator end() const + { + return m_properties.cend(); + } + + std::vector> get_properties_vector() const + { + std::vector> ret; + for (auto it = m_properties.cbegin(); it != m_properties.cend(); ++it) + { + ret.push_back(it->second); + } + return ret; + } + + const ::odata::utility::string_t get_base_type_name() const + { + return m_baseTypeName; + } + + void set_base_type(const std::shared_ptr& base_entity_type) + { + m_base_type = base_entity_type; + } + + const std::shared_ptr& get_base_type() + { + return m_base_type; + } + + bool is_open_type() const + { + return m_is_openType; + } + + /// + /// Looks up a property of the type by name. + /// + /// The name of the property. + /// A pointer to the property if found, an empty pointer otherwise. + ODATACPP_API std::shared_ptr find_property(::odata::utility::string_t name) const; + +protected: + friend class edm_schema; + + ::odata::utility::string_t m_baseTypeName; + std::shared_ptr m_base_type; + + bool m_is_abstract; + bool m_is_openType; + + std::unordered_map<::odata::utility::string_t, std::shared_ptr> m_properties; +}; + +class edm_enum_member +{ +public: + /// + /// Constructor + /// + edm_enum_member() + { + } + + + edm_enum_member(::odata::utility::string_t name, unsigned long value) : m_name(name), m_value(value) + { + } + + const ::odata::utility::string_t& get_enum_member_name() const + { + return m_name; + } + void set_enum_member_name(const ::odata::utility::string_t& name) + { + m_name = name; + } + + unsigned long get_enum_member_value() const + { + return m_value; + } + + void set_enum_member_value(unsigned long value) + { + m_value = value; + } + +private: + friend class edm_enum_type; + + ::odata::utility::string_t m_name; + unsigned long m_value; +}; + +/// +/// +/// +class edm_enum_type : public edm_named_type +{ +public: + /// + /// Constructor + /// + edm_enum_type(::odata::utility::string_t name, ::odata::utility::string_t name_space, ::odata::utility::string_t underlying_type, bool is_flag) + : edm_named_type(name, name_space, edm_type_kind_t::Enum), m_underlying_type(underlying_type), m_is_flag(is_flag) + { + } + + /// + /// + /// + void add_enum_member(std::shared_ptr member) + { + m_members.push_back(member); + } + + const std::vector>& get_enum_members() + { + return m_members; + } + + bool is_flag() + { + return m_is_flag; + } + +private: + friend class edm_schema; + + ::odata::utility::string_t m_underlying_type; + bool m_is_flag; + + std::vector> m_members; +}; + +/// +/// +/// +class edm_complex_type : public edm_structured_type +{ +public: + /// + /// Constructor + /// + /// The name of the type. + edm_complex_type(::odata::utility::string_t name, ::odata::utility::string_t name_space, + ::odata::utility::string_t basetype = U(""), bool isAbstract = false, bool isOpenType = false) + : edm_structured_type(name, name_space, edm_type_kind_t::Complex, basetype, isAbstract, isOpenType) + { + } + +private: + friend class edm_schema; +}; + +/// +/// +/// +class edm_navigation_source; + +class edm_navigation_type : public edm_named_type +{ +public: + edm_navigation_type(::odata::utility::string_t naviagtion_type, ::odata::utility::string_t partner_name, bool is_contained = false) + : edm_named_type(naviagtion_type, U(""), edm_type_kind_t::Navigation), m_partner_name(partner_name), m_is_contained(is_contained) + { + m_naviagtion_type = std::make_shared(naviagtion_type, U(""), edm_type_kind_t::Unknown); + } + + std::shared_ptr get_navigation_type() const + { + return m_naviagtion_type.lock(); + } + + void set_navigation_type(const std::shared_ptr& naviagtion_type) + { + m_naviagtion_type = naviagtion_type; + } + + std::shared_ptr get_binded_navigation_source() const + { + return m_is_contained ? nullptr : m_binded_navigation_source.lock(); + } + + void set_binded_navigation_source(const std::shared_ptr& entity_set) + { + if (!m_is_contained) + { + m_binded_navigation_source = entity_set; + } + } + + bool is_contained() const + { + return m_is_contained; + } + +private: + friend class edm_schema; + + std::weak_ptr m_naviagtion_type; + ::odata::utility::string_t m_partner_name; + bool m_is_contained; + // only works for navigation that has m_is_contained = false + std::weak_ptr m_binded_navigation_source; +}; + +/// +/// +/// +enum EdmOperationKind +{ + Action = 0, + Function +}; + +/// +/// +/// +class edm_operation_parameter +{ +public: + edm_operation_parameter() + { + + } + + edm_operation_parameter(::odata::utility::string_t param_name, std::shared_ptr param_type) : + m_param_name(param_name), m_param_type(param_type) + { + + } + + const std::shared_ptr& get_param_type() const + { + return m_param_type; + } + + void set_param_type(std::shared_ptr type) + { + m_param_type = type; + } + + const ::odata::utility::string_t& get_param_name() const + { + return m_param_name; + } + +private: + friend class edm_operation_type; + + ::odata::utility::string_t m_param_name; + std::shared_ptr m_param_type; + bool m_is_nullable; +}; + +/// +/// +/// +class edm_operation_type : public edm_named_type +{ +public: + edm_operation_type(::odata::utility::string_t name, ::odata::utility::string_t name_space, bool is_bound, ::odata::utility::string_t path, EdmOperationKind operation_kind, bool is_composable) + : edm_named_type(name, name_space, edm_type_kind_t::Operation), m_path(path), m_is_bound(is_bound), m_operation_kind(operation_kind), m_is_composable(is_composable) + { + if (operation_kind == EdmOperationKind::Action) + { + m_is_composable = false; + } + } + + void add_operation_parameter(std::shared_ptr parameter) + { + m_parameters.push_back(parameter); + } + + void set_return_type(std::shared_ptr return_type) + { + m_return_type = return_type; + } + + const std::vector>& get_operation_parameters() + { + return m_parameters; + } + + void set_return_type_name(const ::odata::utility::string_t& return_type_name) + { + m_return_type_name = return_type_name; + } + + const ::odata::utility::string_t& get_return_type_name() const + { + return m_return_type_name; + } + + std::shared_ptr get_operation_return_type() + { + return m_return_type; + } + + bool is_bound() + { + return m_is_bound; + } + + bool is_function() + { + return m_operation_kind == EdmOperationKind::Function; + } + + bool is_composable() + { + return m_is_composable; + } + +private: + friend class edm_schema; + friend class edm_model; + + bool m_is_bound; + ::odata::utility::string_t m_path; + std::vector> m_parameters; + std::shared_ptr m_return_type; + ::odata::utility::string_t m_return_type_name; + EdmOperationKind m_operation_kind; + bool m_is_composable; +}; + + +/// +/// Represents the type of an entity in a container +/// +class edm_entity_type : public edm_structured_type +{ +public: + /// + /// Constructor + /// + /// The name of the type. + edm_entity_type(::odata::utility::string_t name, ::odata::utility::string_t name_space, + ::odata::utility::string_t basetype = U(""), bool isAbstract = false, bool isOpenType = false, bool hasStream = false) + : edm_structured_type(name, name_space, edm_type_kind_t::Entity, basetype, isAbstract, isOpenType), m_HasStream(hasStream) + { + } + + /// + /// Adds a property name to the key of the entity type + /// + void add_key_property(const ::odata::utility::string_t& property_ref) + { + m_key.push_back(property_ref); + } + + /// + /// Gets the key, a collection of names of properties in the entity. + /// + const std::vector<::odata::utility::string_t>& key() const + { + return m_key; + } + + std::vector<::odata::utility::string_t> get_key_with_parents() const + { + std::vector<::odata::utility::string_t> ret = m_key; + + if (m_base_type && std::dynamic_pointer_cast(m_base_type)) + { + auto parent_keys = (std::dynamic_pointer_cast(m_base_type))->get_key_with_parents(); + if (parent_keys.size() > 0) + { + ret.insert(ret.end(), parent_keys.begin(), parent_keys.end()); + } + } + + return ret; + } + +private: + friend class edm_schema; + + std::vector<::odata::utility::string_t> m_key; + bool m_HasStream; +}; + +}} \ No newline at end of file diff --git a/include/odata/edm/odata_edm.h b/include/odata/edm/odata_edm.h new file mode 100644 index 0000000..53dae72 --- /dev/null +++ b/include/odata/edm/odata_edm.h @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "odata/edm/edm_type.h" +#include "odata/edm/edm_entity_set.h" +#include "odata/edm/edm_operation_import.h" +#include "odata/edm/edm_entity_container.h" +#include "odata/edm/edm_schema.h" +#include "odata/edm/edm_model.h" +#include "odata/edm/edm_singleton.h" +#include "odata/edm/edm_model_reader.h" diff --git a/msvc/Common.Build.Traversal.targets b/msvc/Common.Build.Traversal.targets new file mode 100644 index 0000000..a494de7 --- /dev/null +++ b/msvc/Common.Build.Traversal.targets @@ -0,0 +1,28 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + true + + + + + + + + + + + + + + + + diff --git a/msvc/Common.Build.settings b/msvc/Common.Build.settings new file mode 100644 index 0000000..8eb5f62 --- /dev/null +++ b/msvc/Common.Build.settings @@ -0,0 +1,235 @@ + + + + + + + $(MSBuildAllProjects);$(MSBuildThisFileFullPath) + + + + + + Debug + Win32 + + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.root)) + $(BuildRoot)\msvc + $(registry:HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion@CurrentVersion) + + 120 + 110 + + $(WindowsSdkDir) + true + true + false + + + + + + $(BuildRoot)\output\$(Configuration)\ + $(OutputPath) + $(BuildRoot)\Release\Tests + $(BuildRoot)\Release\libs\websocketpp + $(BuildRoot)\packages + $(BuildRoot)\..\Tools\packages + $(BuildRoot)\Release\Collateral + $(BuildRoot)\..\Tools\poco\poco$(DevToolsVersion) + + + + + $(OutDir)\ + + + + $(VS110COMNTOOLS)..\IDE + + + + $(VS120COMNTOOLS)..\IDE + + + + $(TargetsPath)\BinaryDependencies + prompt + 4 + + + false + + + false + $(RunCodeAnalysis) + true + + true + + + true + false + false + true + + + + + Level4 + Use + true + $(EnableCPPAnalysis) + true + + + + + + Disabled + WIN32;_DEBUG;%(PreprocessorDefinitions) + + + true + + + + + + _WIN64;_DEBUG;%(PreprocessorDefinitions) + + + true + + + + + + WIN32;_DEBUG;%(PreprocessorDefinitions) + + + true + + + + + + _WIN64;NDEBUG;%(PreprocessorDefinitions) + + + true + + + + + + WIN32;NDEBUG;%(PreprocessorDefinitions) + + + true + + + + + + WIN32;NDEBUG;%(PreprocessorDefinitions) + + + true + + + + + true + full + false + DEBUG;TRACE + x86 + + + + true + full + false + DEBUG;TRACE;X64 + + + + pdbonly + true + TRACE + x86 + + + + pdbonly + true + TRACE;X64 + + + + true + full + false + DEBUG;TRACE;ARM + + + + pdbonly + true + TRACE;ARM + + + + + + + + + + + + + + + + + + + + + + + + + + + + + <_ResolvedProjectReferencePaths> + Undefined + + + + + + + + <_ResolvedProjectReferencePaths Condition="'%(CopyToOutputDirectory)' != 'Undefined'" + Remove="@(_ResolvedProjectReferencePaths)" /> + + + + + + + diff --git a/msvc/dirs.proj b/msvc/dirs.proj new file mode 100644 index 0000000..f3a1ee6 --- /dev/null +++ b/msvc/dirs.proj @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/msvc/vs11/Common.vs11.props b/msvc/vs11/Common.vs11.props new file mode 100644 index 0000000..af34f95 --- /dev/null +++ b/msvc/vs11/Common.vs11.props @@ -0,0 +1,28 @@ + + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.root)) + $(ODataCppBase)\output + $(ODataCppBase)\include + $(ODataCppBase)\src + $(ODataCppBase)\lib + $(ODataCppBase)\tools + $(ODataCppBase)\tests + $(ODataCppTest)\functional + $(ODataCppTest)\e2e + $(ODataCppTest)\e2e_service + $(ODataCppTest)\service + $(ODataCppBase)\samples + + + + + $(ODataCppTest)\framework + $(TestFrameworkBase)\UnitTestpp + $(UnitTestppBase)\src + $(TestFrameworkBase)\TestRunner + $(TestFrameworkBase)\utilities + $(UtilitiesBase)\include + + diff --git a/msvc/vs11/TestRunner.vs11.vcxproj b/msvc/vs11/TestRunner.vs11.vcxproj new file mode 100644 index 0000000..86360db --- /dev/null +++ b/msvc/vs11/TestRunner.vs11.vcxproj @@ -0,0 +1,247 @@ + + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {6490C580-DD4A-4F2D-A345-732C5148349F} + Win32Proj + TestRunner + $(VCTargetsPath11) + SAK + SAK + SAK + SAK + TestRunner.vs11 + .\ + + + + Application + true + Unicode + v110 + + + Application + true + Unicode + v110 + + + Application + true + Unicode + v110 + + + Application + false + true + Unicode + v110 + + + Application + false + true + Unicode + v110 + + + Application + false + true + Unicode + v110 + + + + + + + + + + + + + + + + + + + + + + TestRunner + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + + + TestRunner + + + TestRunner + $(ODataCppOut)\$(Platform)\$(Configuration)\ + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + + + false + TestRunner + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + + + false + TestRunner + + + false + TestRunner + $(ODataCppOut)\$(Platform)\$(Configuration)\ + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + + + + + + + + + + NotUsing + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + $(IntDir)$(TargetName).int.pdb + + + Console + true + + + + + NotUsing + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + + + + + NotUsing + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + $(IntDir)$(TargetName).int.pdb + + + Console + true + true + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + true + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + true + true + + + + + + + + + + + + {3eb86c0d-432c-4ffc-bad4-8df4efc7d0ff} + + + + \ No newline at end of file diff --git a/msvc/vs11/TestRunner.vs11.vcxproj.filters b/msvc/vs11/TestRunner.vs11.vcxproj.filters new file mode 100644 index 0000000..53c47b3 --- /dev/null +++ b/msvc/vs11/TestRunner.vs11.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + Source Files + + + Source Files + + + + + {5e4d32c8-a472-4388-b880-920a7a546fa3} + + + {f05a925c-282c-4424-ae9a-10fe7f88fe47} + + + + + Header Files + + + \ No newline at end of file diff --git a/msvc/vs11/UnitTestpp.vs11.vcxproj b/msvc/vs11/UnitTestpp.vs11.vcxproj new file mode 100644 index 0000000..bbf1c5a --- /dev/null +++ b/msvc/vs11/UnitTestpp.vs11.vcxproj @@ -0,0 +1,244 @@ + + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF} + Win32Proj + UnitTest + SAK + SAK + SAK + SAK + $(VCTargetsPath11) + UnitTestpp.vs11 + .\ + + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + + $(ODataCppOut)\$(Configuration)\ + $(UnitTestppSrc);$(IncludePath) + + + $(ODataCppOut)\$(Configuration)\ + $(UnitTestppSrc);$(IncludePath) + + + $(UnitTestppSrc);$(IncludePath) + + + $(UnitTestppSrc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + $(UnitTestppSrc);$(IncludePath) + + + $(UnitTestppSrc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + + + + Use + + + + + NotUsing + Disabled + UNITTEST_DLL_EXPORT;WIN32;_DEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + + + + + NotUsing + Disabled + UNITTEST_DLL_EXPORT;WIN32;_DEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + + + + + NotUsing + Disabled + UNITTEST_DLL_EXPORT;WIN32;_DEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + + + + + MaxSpeed + NotUsing + true + true + UNITTEST_DLL_EXPORT;WIN32;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + true + true + + + + + MaxSpeed + NotUsing + true + true + UNITTEST_DLL_EXPORT;WIN32;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + MaxSpeed + NotUsing + true + true + UNITTEST_DLL_EXPORT;WIN32;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/vs11/UnitTestpp.vs11.vcxproj.filters b/msvc/vs11/UnitTestpp.vs11.vcxproj.filters new file mode 100644 index 0000000..f32af04 --- /dev/null +++ b/msvc/vs11/UnitTestpp.vs11.vcxproj.filters @@ -0,0 +1,156 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + {640fb93d-0deb-4bc4-a035-5d44d5c2af4b} + + + {b883825b-c6c4-4545-806e-dfe105753ac8} + + + \ No newline at end of file diff --git a/msvc/vs11/dirs.proj b/msvc/vs11/dirs.proj new file mode 100644 index 0000000..3e9c6d5 --- /dev/null +++ b/msvc/vs11/dirs.proj @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/msvc/vs11/odata_functional_test.vs11.vcxproj b/msvc/vs11/odata_functional_test.vs11.vcxproj new file mode 100644 index 0000000..50c3256 --- /dev/null +++ b/msvc/vs11/odata_functional_test.vs11.vcxproj @@ -0,0 +1,116 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + + {4bf94744-fa07-4bd2-8d41-fb292dc6d129} + + + {6490c580-dd4a-4f2d-a345-732c5148349f} + + + {3eb86c0d-432c-4ffc-bad4-8df4efc7d0ff} + + + + + + + + + + + + + + + + + + + + + + + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC} + odatacpp_full + .\ + odata_functional_test.vs11 + + + + DynamicLibrary + true + v110 + Unicode + + + DynamicLibrary + false + v110 + true + Unicode + + + + + + + + + + + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + $(ODataCppOut)\$(Configuration)\ + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ODataCppOut)\$(Configuration)\ + + + + Level1 + Disabled + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + true + + + + + Level1 + MaxSpeed + true + true + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + true + true + true + + + + \ No newline at end of file diff --git a/msvc/vs11/odata_functional_test.vs11.vcxproj.filters b/msvc/vs11/odata_functional_test.vs11.vcxproj.filters new file mode 100644 index 0000000..1d2f0d1 --- /dev/null +++ b/msvc/vs11/odata_functional_test.vs11.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {5fa9fb1e-d039-4ca7-bf2f-8a4e9c8b2bd7} + + + {b36732de-b9f7-4675-8b9c-90edc093b06b} + + + {2deb115a-262a-404a-bfaa-163b6c2be658} + + + + + common_test + + + common_test + + + core_test + + + core_test + + + core_test + + + core_test + + + core_test + + + core_test + + + edm_test + + + edm_test + + + + + + + \ No newline at end of file diff --git a/msvc/vs11/odata_library.vs11.vcxproj b/msvc/vs11/odata_library.vs11.vcxproj new file mode 100644 index 0000000..33c319e --- /dev/null +++ b/msvc/vs11/odata_library.vs11.vcxproj @@ -0,0 +1,209 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + {4BF94744-FA07-4BD2-8D41-FB292DC6D129} + odatacpp_full + .\ + odata_library.vs11 + + + + DynamicLibrary + true + v110 + Unicode + + + DynamicLibrary + false + v110 + true + Unicode + + + + + + + + + + + + + $(ODataCppOut)\$(Configuration)\ + $(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + + + $(ODataCppOut)\$(Configuration)\ + $(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + + + + Level3 + Disabled + true + WIN32;_USRDLL;ODATALIB_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + true + xmllite.lib;%(AdditionalDependencies) + + + + + + + + + Level3 + MaxSpeed + true + true + true + WIN32;_USRDLL;ODATALIB_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + false + true + true + xmllite.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/vs11/odata_library.vs11.vcxproj.filters b/msvc/vs11/odata_library.vs11.vcxproj.filters new file mode 100644 index 0000000..1fe0d8b --- /dev/null +++ b/msvc/vs11/odata_library.vs11.vcxproj.filters @@ -0,0 +1,348 @@ + + + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + common + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + common + + + common + + + common + + + common + + + core + + + core + + + common + + + common + + + common\compat + + + common\compat + + + common\compat + + + common\compat + + + common + + + common + + + common + + + common + + + common + + + + + {7c820968-fa0e-4e63-aa7a-ded758c83303} + + + {9321f1b2-2288-40db-aaad-9eceaf3004af} + + + {80471e96-4ce8-4de2-be1c-e69b47a31574} + + + {2c8cd61e-c0c2-4396-bac1-dad850b3078b} + + + \ No newline at end of file diff --git a/msvc/vs11/odatacpp.sln b/msvc/vs11/odatacpp.sln new file mode 100644 index 0000000..e608d4d --- /dev/null +++ b/msvc/vs11/odatacpp.sln @@ -0,0 +1,80 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2012 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "odata_library.vs11", "odata_library.vs11.vcxproj", "{4BF94744-FA07-4BD2-8D41-FB292DC6D129}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestRunner.vs11", "TestRunner.vs11.vcxproj", "{6490C580-DD4A-4F2D-A345-732C5148349F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTestpp.vs11", "UnitTestpp.vs11.vcxproj", "{3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "utilities.vs11", "utilities.vs11.vcxproj", "{0C9D50D9-94FB-4732-A4AD-58E068315BB2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "odata_functional_test.vs11", "odata_functional_test.vs11.vcxproj", "{2D803F68-D527-44A4-AD67-49D2A1E2FCDC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|ARM = Release|ARM + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|ARM.ActiveCfg = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|Win32.ActiveCfg = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|Win32.Build.0 = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|x64.ActiveCfg = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|ARM.ActiveCfg = Release|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|Win32.ActiveCfg = Release|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|Win32.Build.0 = Release|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|x64.ActiveCfg = Release|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|ARM.ActiveCfg = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|ARM.Build.0 = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|Win32.ActiveCfg = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|Win32.Build.0 = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|x64.ActiveCfg = Debug|x64 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|x64.Build.0 = Debug|x64 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|ARM.ActiveCfg = Release|ARM + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|ARM.Build.0 = Release|ARM + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|Win32.ActiveCfg = Release|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|Win32.Build.0 = Release|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|x64.ActiveCfg = Release|x64 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|x64.Build.0 = Release|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|ARM.ActiveCfg = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|ARM.Build.0 = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|Win32.Build.0 = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|x64.ActiveCfg = Debug|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|x64.Build.0 = Debug|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|ARM.ActiveCfg = Release|ARM + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|ARM.Build.0 = Release|ARM + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|Win32.ActiveCfg = Release|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|Win32.Build.0 = Release|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|x64.ActiveCfg = Release|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|x64.Build.0 = Release|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|ARM.ActiveCfg = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|ARM.Build.0 = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|Win32.ActiveCfg = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|Win32.Build.0 = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|x64.ActiveCfg = Debug|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|x64.Build.0 = Debug|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|ARM.ActiveCfg = Release|ARM + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|ARM.Build.0 = Release|ARM + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|Win32.ActiveCfg = Release|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|Win32.Build.0 = Release|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|x64.ActiveCfg = Release|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|x64.Build.0 = Release|x64 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|ARM.ActiveCfg = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|Win32.ActiveCfg = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|Win32.Build.0 = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|x64.ActiveCfg = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|ARM.ActiveCfg = Release|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|Win32.ActiveCfg = Release|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|Win32.Build.0 = Release|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|x64.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/msvc/vs11/test_only/odata_e2e_service.vs11.vcxproj b/msvc/vs11/test_only/odata_e2e_service.vs11.vcxproj new file mode 100644 index 0000000..fa24ca1 --- /dev/null +++ b/msvc/vs11/test_only/odata_e2e_service.vs11.vcxproj @@ -0,0 +1,119 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + + {4bf94744-fa07-4bd2-8d41-fb292dc6d129} + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + {72AD0AEA-BB68-4F84-BFE5-392BF90FC729} + odatacpp_full + .\ + odata_e2e_service.vs11 + + + + Application + true + v110 + Unicode + + + Application + false + v110 + true + Unicode + + + + + + + + + + + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + $(ODataCppOut)\$(Configuration)\ + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ODataCppOut)\$(Configuration)\ + + + + Level1 + Disabled + true + _CRT_SECURE_NO_WARNINGS;WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /bigobj + C:\Git\OData\mongo-cxx-driver\build\install\include;C:\boost_1_55_0-msvc-11.0-32 + + + true + C:\boost_1_55_0-msvc-11.0-32\lib32-msvc-11.0;C:\Git\OData\mongo-cxx-driver\build\install\lib + mongoclient-gd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level1 + MaxSpeed + true + true + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + + + true + true + true + + + + \ No newline at end of file diff --git a/msvc/vs11/test_only/odata_service.vs11.vcxproj b/msvc/vs11/test_only/odata_service.vs11.vcxproj new file mode 100644 index 0000000..97c6488 --- /dev/null +++ b/msvc/vs11/test_only/odata_service.vs11.vcxproj @@ -0,0 +1,105 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + + {4bf94744-fa07-4bd2-8d41-fb292dc6d129} + + + + + + + + + + + + Always + + + + {E6BC482F-7D56-41DA-A331-1268DABCC03F} + odatacpp_full + .\ + odata_service.vs11 + + + + Application + true + v110 + Unicode + + + Application + false + v110 + true + Unicode + + + + + + + + + + + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + $(ODataCppOut)\$(Configuration)\ + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ODataCppOut)\$(Configuration)\ + + + + Level1 + Disabled + true + _CRT_SECURE_NO_WARNINGS;WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /bigobj + + + + + true + + + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level1 + MaxSpeed + true + true + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + + + true + true + true + + + + \ No newline at end of file diff --git a/msvc/vs11/utilities.vs11.vcxproj b/msvc/vs11/utilities.vs11.vcxproj new file mode 100644 index 0000000..96cc8ef --- /dev/null +++ b/msvc/vs11/utilities.vs11.vcxproj @@ -0,0 +1,203 @@ + + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {0c9d50d9-94fb-4732-a4ad-58e068315bb2} + Win32Proj + CommonUtilities110 + SAK + SAK + SAK + SAK + $(VCTargetsPath11) + .\ + + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + DynamicLibrary + false + true + Unicode + v110 + + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + + NotUsing + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + $(OutDir)$(TargetName).lib + + + + + Use + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + Advapi32.lib;kernel32.lib;user32.lib;%(AdditionalDependencies) + + + + + NotUsing + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + true + true + + + + + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + Advapi32.lib;kernel32.lib;user32.lib;%(AdditionalDependencies) + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + {6490c580-dd4a-4f2d-a345-732c5148349f} + + + {3eb86c0d-432c-4ffc-bad4-8df4efc7d0ff} + + + + \ No newline at end of file diff --git a/msvc/vs11/utilities.vs11.vcxproj.filters b/msvc/vs11/utilities.vs11.vcxproj.filters new file mode 100644 index 0000000..2f1da81 --- /dev/null +++ b/msvc/vs11/utilities.vs11.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + Source Files + + + + + Header Files + + + Header Files + + + + + {24e13f0e-e0f6-4fe0-922a-5cf7cbe823f4} + + + {ef18a939-f1a3-4e9e-b93a-05826fbae7f6} + + + \ No newline at end of file diff --git a/msvc/vs12/Common.vs12.props b/msvc/vs12/Common.vs12.props new file mode 100644 index 0000000..84dcd28 --- /dev/null +++ b/msvc/vs12/Common.vs12.props @@ -0,0 +1,28 @@ + + + + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), build.root)) + $(ODataCppBase)\output + $(ODataCppBase)\include + $(ODataCppBase)\src + $(ODataCppBase)\lib + $(ODataCppBase)\tools + $(ODataCppBase)\tests + $(ODataCppTest)\functional + $(ODataCppTest)\e2e + $(ODataCppTest)\e2e_service + $(ODataCppTest)\service + $(ODataCppBase)\samples + + + + + $(ODataCppTest)\framework + $(TestFrameworkBase)\UnitTestpp + $(UnitTestppBase)\src + $(TestFrameworkBase)\TestRunner + $(TestFrameworkBase)\utilities + $(UtilitiesBase)\include + + diff --git a/msvc/vs12/TestRunner.vs12.vcxproj b/msvc/vs12/TestRunner.vs12.vcxproj new file mode 100644 index 0000000..e8f17f9 --- /dev/null +++ b/msvc/vs12/TestRunner.vs12.vcxproj @@ -0,0 +1,249 @@ + + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {6490C580-DD4A-4F2D-A345-732C5148349F} + Win32Proj + TestRunner + $(VCTargetsPath12) + SAK + SAK + SAK + SAK + TestRunner.vs12 + .\ + + + + Application + true + Unicode + v120 + + + Application + true + Unicode + v120 + + + Application + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + Application + false + true + Unicode + v120 + + + + + + + + + + + + + + + + + + + + + + TestRunner + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + TestRunner + + + TestRunner + $(ODataCppOut)\$(Platform)\$(Configuration)\ + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + + + false + TestRunner + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + false + TestRunner + + + false + TestRunner + $(ODataCppOut)\$(Platform)\$(Configuration)\ + $(UnitTestppBase);$(ODataCppInc);$(IncludePath) + + + + + + + + + + NotUsing + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + $(IntDir)$(TargetName).int.pdb + + + Console + true + + + + + NotUsing + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + + + + + NotUsing + Disabled + WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + $(IntDir)$(TargetName).int.pdb + + + Console + true + true + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + true + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + + + false + + + Console + true + true + true + + + + + + + + + + + + {3eb86c0d-432c-4ffc-bad4-8df4efc7d0ff} + + + + \ No newline at end of file diff --git a/msvc/vs12/TestRunner.vs12.vcxproj.filters b/msvc/vs12/TestRunner.vs12.vcxproj.filters new file mode 100644 index 0000000..53c47b3 --- /dev/null +++ b/msvc/vs12/TestRunner.vs12.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + Source Files + + + Source Files + + + + + {5e4d32c8-a472-4388-b880-920a7a546fa3} + + + {f05a925c-282c-4424-ae9a-10fe7f88fe47} + + + + + Header Files + + + \ No newline at end of file diff --git a/msvc/vs12/UnitTestpp.vs12.vcxproj b/msvc/vs12/UnitTestpp.vs12.vcxproj new file mode 100644 index 0000000..50b1896 --- /dev/null +++ b/msvc/vs12/UnitTestpp.vs12.vcxproj @@ -0,0 +1,246 @@ + + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF} + Win32Proj + UnitTest + SAK + SAK + SAK + SAK + $(VCTargetsPath12) + UnitTestpp.vs12 + .\ + + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + $(ODataCppOut)\$(Configuration)\ + $(UnitTestppSrc);$(IncludePath) + $(Configuration)\$(ProjectName)\ + + + $(ODataCppOut)\$(Configuration)\ + $(UnitTestppSrc);$(IncludePath) + $(Configuration)\$(ProjectName)\ + + + $(UnitTestppSrc);$(IncludePath) + + + $(UnitTestppSrc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + $(UnitTestppSrc);$(IncludePath) + + + $(UnitTestppSrc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + + + + Use + + + + + NotUsing + Disabled + UNITTEST_DLL_EXPORT;WIN32;_DEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + + + + + NotUsing + Disabled + UNITTEST_DLL_EXPORT;WIN32;_DEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + + + + + NotUsing + Disabled + UNITTEST_DLL_EXPORT;WIN32;_DEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + + + + + MaxSpeed + NotUsing + true + true + UNITTEST_DLL_EXPORT;WIN32;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + true + true + + + + + MaxSpeed + NotUsing + true + true + UNITTEST_DLL_EXPORT;WIN32;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + MaxSpeed + NotUsing + true + true + UNITTEST_DLL_EXPORT;WIN32;NDEBUG;_USRDLL;_CRT_SECURE_NO_DEPRECATE;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/vs12/UnitTestpp.vs12.vcxproj.filters b/msvc/vs12/UnitTestpp.vs12.vcxproj.filters new file mode 100644 index 0000000..f32af04 --- /dev/null +++ b/msvc/vs12/UnitTestpp.vs12.vcxproj.filters @@ -0,0 +1,156 @@ + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + + + {640fb93d-0deb-4bc4-a035-5d44d5c2af4b} + + + {b883825b-c6c4-4545-806e-dfe105753ac8} + + + \ No newline at end of file diff --git a/msvc/vs12/dirs.proj b/msvc/vs12/dirs.proj new file mode 100644 index 0000000..6196d5e --- /dev/null +++ b/msvc/vs12/dirs.proj @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/msvc/vs12/odata_functional_test.vs12.vcxproj b/msvc/vs12/odata_functional_test.vs12.vcxproj new file mode 100644 index 0000000..6083c60 --- /dev/null +++ b/msvc/vs12/odata_functional_test.vs12.vcxproj @@ -0,0 +1,121 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + + {4bf94744-fa07-4bd2-8d41-fb292dc6d129} + + + {6490c580-dd4a-4f2d-a345-732c5148349f} + + + {3eb86c0d-432c-4ffc-bad4-8df4efc7d0ff} + + + + + + + + + + + + + + + + + + + + + + + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC} + odatacpp_full + .\ + odata_functional_test.vs12 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + + Level1 + Disabled + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + true + + + + + Level1 + MaxSpeed + true + true + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + true + true + true + + + + + + + \ No newline at end of file diff --git a/msvc/vs12/odata_functional_test.vs12.vcxproj.filters b/msvc/vs12/odata_functional_test.vs12.vcxproj.filters new file mode 100644 index 0000000..1d2f0d1 --- /dev/null +++ b/msvc/vs12/odata_functional_test.vs12.vcxproj.filters @@ -0,0 +1,50 @@ + + + + + {5fa9fb1e-d039-4ca7-bf2f-8a4e9c8b2bd7} + + + {b36732de-b9f7-4675-8b9c-90edc093b06b} + + + {2deb115a-262a-404a-bfaa-163b6c2be658} + + + + + common_test + + + common_test + + + core_test + + + core_test + + + core_test + + + core_test + + + core_test + + + core_test + + + edm_test + + + edm_test + + + + + + + \ No newline at end of file diff --git a/msvc/vs12/odata_library.vs12.vcxproj b/msvc/vs12/odata_library.vs12.vcxproj new file mode 100644 index 0000000..98b0e83 --- /dev/null +++ b/msvc/vs12/odata_library.vs12.vcxproj @@ -0,0 +1,211 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + {4BF94744-FA07-4BD2-8D41-FB292DC6D129} + odatacpp_full + .\ + odata_library.vs12 + + + + DynamicLibrary + true + v120 + Unicode + + + DynamicLibrary + false + v120 + true + Unicode + + + + + + + + + + + + + $(ODataCppOut)\$(Configuration)\ + $(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + $(Configuration)\$(ProjectName)\ + + + $(ODataCppOut)\$(Configuration)\ + $(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(Configuration)\$(ProjectName)\ + + + + Level3 + Disabled + true + WIN32;_USRDLL;ODATALIB_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + true + xmllite.lib;%(AdditionalDependencies) + + + + + + + + + Level3 + MaxSpeed + true + true + true + WIN32;_USRDLL;ODATALIB_EXPORTS;%(PreprocessorDefinitions) + /Zm200 + $(IntDir)$(TargetName).int.pdb + + + false + true + true + xmllite.lib;%(AdditionalDependencies) + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/msvc/vs12/odata_library.vs12.vcxproj.filters b/msvc/vs12/odata_library.vs12.vcxproj.filters new file mode 100644 index 0000000..1fe0d8b --- /dev/null +++ b/msvc/vs12/odata_library.vs12.vcxproj.filters @@ -0,0 +1,348 @@ + + + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + common + + + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + edm + + + common + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + core + + + common + + + common + + + common + + + common + + + core + + + core + + + common + + + common + + + common\compat + + + common\compat + + + common\compat + + + common\compat + + + common + + + common + + + common + + + common + + + common + + + + + {7c820968-fa0e-4e63-aa7a-ded758c83303} + + + {9321f1b2-2288-40db-aaad-9eceaf3004af} + + + {80471e96-4ce8-4de2-be1c-e69b47a31574} + + + {2c8cd61e-c0c2-4396-bac1-dad850b3078b} + + + \ No newline at end of file diff --git a/msvc/vs12/odatacpp.sln b/msvc/vs12/odatacpp.sln new file mode 100644 index 0000000..fcb482a --- /dev/null +++ b/msvc/vs12/odatacpp.sln @@ -0,0 +1,82 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 2013 +VisualStudioVersion = 12.0.31101.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "odata_library.vs12", "odata_library.vs12.vcxproj", "{4BF94744-FA07-4BD2-8D41-FB292DC6D129}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "TestRunner.vs12", "TestRunner.vs12.vcxproj", "{6490C580-DD4A-4F2D-A345-732C5148349F}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "UnitTestpp.vs12", "UnitTestpp.vs12.vcxproj", "{3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "utilities.vs12", "utilities.vs12.vcxproj", "{0C9D50D9-94FB-4732-A4AD-58E068315BB2}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "odata_functional_test.vs12", "odata_functional_test.vs12.vcxproj", "{2D803F68-D527-44A4-AD67-49D2A1E2FCDC}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|ARM = Debug|ARM + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|ARM = Release|ARM + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|ARM.ActiveCfg = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|Win32.ActiveCfg = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|Win32.Build.0 = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Debug|x64.ActiveCfg = Debug|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|ARM.ActiveCfg = Release|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|Win32.ActiveCfg = Release|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|Win32.Build.0 = Release|Win32 + {4BF94744-FA07-4BD2-8D41-FB292DC6D129}.Release|x64.ActiveCfg = Release|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|ARM.ActiveCfg = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|ARM.Build.0 = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|Win32.ActiveCfg = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|Win32.Build.0 = Debug|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|x64.ActiveCfg = Debug|x64 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Debug|x64.Build.0 = Debug|x64 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|ARM.ActiveCfg = Release|ARM + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|ARM.Build.0 = Release|ARM + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|Win32.ActiveCfg = Release|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|Win32.Build.0 = Release|Win32 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|x64.ActiveCfg = Release|x64 + {6490C580-DD4A-4F2D-A345-732C5148349F}.Release|x64.Build.0 = Release|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|ARM.ActiveCfg = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|ARM.Build.0 = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|Win32.ActiveCfg = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|Win32.Build.0 = Debug|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|x64.ActiveCfg = Debug|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Debug|x64.Build.0 = Debug|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|ARM.ActiveCfg = Release|ARM + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|ARM.Build.0 = Release|ARM + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|Win32.ActiveCfg = Release|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|Win32.Build.0 = Release|Win32 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|x64.ActiveCfg = Release|x64 + {3EB86C0D-432C-4FFC-BAD4-8DF4EFC7D0FF}.Release|x64.Build.0 = Release|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|ARM.ActiveCfg = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|ARM.Build.0 = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|Win32.ActiveCfg = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|Win32.Build.0 = Debug|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|x64.ActiveCfg = Debug|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Debug|x64.Build.0 = Debug|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|ARM.ActiveCfg = Release|ARM + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|ARM.Build.0 = Release|ARM + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|Win32.ActiveCfg = Release|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|Win32.Build.0 = Release|Win32 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|x64.ActiveCfg = Release|x64 + {0C9D50D9-94FB-4732-A4AD-58E068315BB2}.Release|x64.Build.0 = Release|x64 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|ARM.ActiveCfg = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|Win32.ActiveCfg = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|Win32.Build.0 = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Debug|x64.ActiveCfg = Debug|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|ARM.ActiveCfg = Release|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|Win32.ActiveCfg = Release|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|Win32.Build.0 = Release|Win32 + {2D803F68-D527-44A4-AD67-49D2A1E2FCDC}.Release|x64.ActiveCfg = Release|Win32 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/msvc/vs12/test_only/odata_e2e_service.vs12.vcxproj b/msvc/vs12/test_only/odata_e2e_service.vs12.vcxproj new file mode 100644 index 0000000..d9c3371 --- /dev/null +++ b/msvc/vs12/test_only/odata_e2e_service.vs12.vcxproj @@ -0,0 +1,119 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + + {4bf94744-fa07-4bd2-8d41-fb292dc6d129} + + + + + + + + + + + + + + + + + + + + + + + + + + + + Always + + + + {72AD0AEA-BB68-4F84-BFE5-392BF90FC729} + odatacpp_full + .\ + odata_e2e_service.vs12 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + $(ODataCppOut)\$(Configuration)\ + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ODataCppOut)\$(Configuration)\ + + + + Level1 + Disabled + true + _CRT_SECURE_NO_WARNINGS;WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /bigobj + C:\Git\OData\mongo-cxx-driver\build\install\include;C:\boost_1_55_0-msvc-11.0-32 + + + true + C:\boost_1_55_0-msvc-11.0-32\lib32-msvc-11.0;C:\Git\OData\mongo-cxx-driver\build\install\lib + mongoclient-gd.lib;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level1 + MaxSpeed + true + true + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + + + true + true + true + + + + \ No newline at end of file diff --git a/msvc/vs12/test_only/odata_service.vs12.vcxproj b/msvc/vs12/test_only/odata_service.vs12.vcxproj new file mode 100644 index 0000000..3e890d4 --- /dev/null +++ b/msvc/vs12/test_only/odata_service.vs12.vcxproj @@ -0,0 +1,106 @@ + + + + + + Debug + Win32 + + + Release + Win32 + + + + + {4bf94744-fa07-4bd2-8d41-fb292dc6d129} + + + + + + + + + + + + Always + + + + {E6BC482F-7D56-41DA-A331-1268DABCC03F} + odatacpp_full + .\ + odata_service.vs12 + + + + Application + true + v120 + Unicode + + + Application + false + v120 + true + Unicode + + + + + + + + + + + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ProjectName)d + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + $(UtilitiesInc);$(UnitTestppBase);$(ODataCppInc);$(IncludePath) + $(ODataCppLib);$(LibraryPath) + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + + Level1 + Disabled + true + _CRT_SECURE_NO_WARNINGS;WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + /bigobj + C:\Git\OData\mongo-cxx-driver\build\install\include;C:\boost_1_55_0-msvc-11.0-32 + + + true + + + kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + + + Level1 + MaxSpeed + true + true + true + WIN32;_USRDLL;COMMONTESTS_EXPORTS;%(PreprocessorDefinitions) + + + true + true + true + + + + \ No newline at end of file diff --git a/msvc/vs12/utilities.vs12.vcxproj b/msvc/vs12/utilities.vs12.vcxproj new file mode 100644 index 0000000..2dd611a --- /dev/null +++ b/msvc/vs12/utilities.vs12.vcxproj @@ -0,0 +1,205 @@ + + + + + + Debug + ARM + + + Debug + Win32 + + + Debug + x64 + + + Release + ARM + + + Release + Win32 + + + Release + x64 + + + + {0c9d50d9-94fb-4732-a4ad-58e068315bb2} + Win32Proj + CommonUtilities120 + SAK + SAK + SAK + SAK + $(VCTargetsPath12) + .\ + + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + DynamicLibrary + false + true + Unicode + v120 + + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Configuration)\ + $(Configuration)\$(ProjectName)\ + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + $(UtilitiesInc);$(IncludePath) + $(ODataCppOut)\$(Platform)\$(Configuration)\ + + + + NotUsing + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + $(OutDir)$(TargetName).lib + + + + + Use + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + Advapi32.lib;kernel32.lib;user32.lib;%(AdditionalDependencies) + + + + + NotUsing + Disabled + WIN32;_DEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + $(IntDir)$(TargetName).int.pdb + + + Windows + true + true + true + + + + + Use + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + Advapi32.lib;kernel32.lib;user32.lib;%(AdditionalDependencies) + + + + + NotUsing + MaxSpeed + true + true + WIN32;NDEBUG;_WINDOWS;_USRDLL;COMMONUTILITIES_EXPORTS;%(PreprocessorDefinitions) + + + Windows + true + true + true + + + + + + + + + + + + + + {6490c580-dd4a-4f2d-a345-732c5148349f} + + + {3eb86c0d-432c-4ffc-bad4-8df4efc7d0ff} + + + + \ No newline at end of file diff --git a/msvc/vs12/utilities.vs12.vcxproj.filters b/msvc/vs12/utilities.vs12.vcxproj.filters new file mode 100644 index 0000000..2f1da81 --- /dev/null +++ b/msvc/vs12/utilities.vs12.vcxproj.filters @@ -0,0 +1,24 @@ + + + + + Source Files + + + + + Header Files + + + Header Files + + + + + {24e13f0e-e0f6-4fe0-922a-5cf7cbe823f4} + + + {ef18a939-f1a3-4e9e-b93a-05826fbae7f6} + + + \ No newline at end of file diff --git a/setup_ps_env_VS2012.ps1 b/setup_ps_env_VS2012.ps1 new file mode 100644 index 0000000..53b4e2c --- /dev/null +++ b/setup_ps_env_VS2012.ps1 @@ -0,0 +1,22 @@ +function Get-Batchfile ($file) { + $cmd = "`"$file`" & set" + cmd /c $cmd | Foreach-Object { + $p, $v = $_.split('=') + Set-Item -path env:$p -value $v + } +} + +function VsVars32() +{ + $vs110comntools = (Get-ChildItem env:VS110COMNTOOLS).Value + $batchFile = [System.IO.Path]::Combine($vs110comntools, "vsvars32.bat") + Get-Batchfile $BatchFile +} + +"Initializing ODataCpp Powershell VS2012 Environment" + +# get VS tools +VsVars32 + +$Env:VisualStudioVersion = "11.0" +$Env:DevToolsVersion = "110" diff --git a/setup_ps_env_VS2013.ps1 b/setup_ps_env_VS2013.ps1 new file mode 100644 index 0000000..ad317b9 --- /dev/null +++ b/setup_ps_env_VS2013.ps1 @@ -0,0 +1,22 @@ +function Get-Batchfile ($file) { + $cmd = "`"$file`" & set" + cmd /c $cmd | Foreach-Object { + $p, $v = $_.split('=') + Set-Item -path env:$p -value $v + } +} + +function VsVars32() +{ + $vs120comntools = (Get-ChildItem env:VS120COMNTOOLS).Value + $batchFile = [System.IO.Path]::Combine($vs120comntools, "vsvars32.bat") + Get-Batchfile $BatchFile +} + +"Initializing ODataCpp Powershell VS2013 Environment" + +# get VS tools +VsVars32 + +$Env:VisualStudioVersion = "12.0" +$Env:DevToolsVersion = "120" diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..297dd2e --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,55 @@ +set(ODATACPP_LIBRARY_SOURCES + common/asyncrt_utils.cpp + common/base64.cpp + common/json.cpp + common/json_parsing.cpp + common/json_serialization.cpp + common/utility.cpp + common/uri.cpp + common/uri_builder.cpp + common/uri_parser.cpp + common/xmlhelpers.cpp + core/odata_context_url_parser.cpp + core/odata_entity_model_builder.cpp + core/odata_entity_value.cpp + core/odata_json_operation_payload_parameter_writer.cpp + core/odata_json_operation_url_parameter_writer.cpp + core/odata_json_reader_full.cpp + core/odata_json_reader_minimal.cpp + core/odata_json_writer.cpp + core/odata_primitive_value.cpp + core/odata_property_map.cpp + core/odata_structured_value.cpp + core/odata_path.cpp + core/odata_path_segment.cpp + core/odata_query_node.cpp + core/odata_path_segment_visitor.cpp + core/odata_query_node_visitor.cpp + core/odata_uri.cpp + core/odata_uri_parser.cpp + edm/edm_entity_container.cpp + edm/edm_model_reader.cpp + edm/edm_schema.cpp + edm/edm_model.cpp + edm/edm_model_utility.cpp + edm/edm_type.cpp + ) + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${WARNINGS} -Werror -pedantic") + +add_library(${ODATACPP_LIBRARY} ${ODATACPP_LIBRARY_SOURCES}) + +target_link_libraries(${ODATACPP_LIBRARY} + ${LIBXML2_LIBRARIES} + ${Boost_FRAMEWORK} + ${Boost_LOCALE_LIBRARY} + ${Boost_SYSTEM_LIBRARY} + ${EXTRALINKS} + ) + +# Portions specific to odatacpp binary versioning. +set (ODATACPP_VERSION_MAJOR 1) +set (ODATACPP_VERSION_MINOR 0) +set (ODATACPP_VERSION_REVISION 0) +set_target_properties(${ODATACPP_LIBRARY} PROPERTIES + SOVERSION ${ODATACPP_VERSION_MAJOR}.${ODATACPP_VERSION_MINOR}) diff --git a/src/common/asyncrt_utils.cpp b/src/common/asyncrt_utils.cpp new file mode 100644 index 0000000..1d7cea9 --- /dev/null +++ b/src/common/asyncrt_utils.cpp @@ -0,0 +1,929 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" +#include + +#if defined(__cplusplus_winrt) +using namespace Platform; +using namespace Windows::Storage::Streams; +#endif // #if !defined(__cplusplus_winrt) + +#ifndef _MS_WINDOWS +#include +#include +using namespace boost::locale::conv; +#endif + +// using namespace odata::web; +using namespace odata::utility; +using namespace odata::utility::conversions; + +namespace odata { namespace utility +{ + +#pragma region error categories + +namespace details +{ + +const std::error_category & __cdecl platform_category() +{ +#ifdef _MS_WINDOWS + return windows_category(); +#else + return linux_category(); +#endif +} + +#ifdef _MS_WINDOWS + +const std::error_category & __cdecl windows_category() +{ + static details::windows_category_impl instance; + return instance; +} + +std::string windows_category_impl::message(int errorCode) const +{ + const size_t buffer_size = 4096; + DWORD dwFlags = FORMAT_MESSAGE_FROM_SYSTEM; + LPCVOID lpSource = NULL; + +#if !defined(__cplusplus_winrt) + if (errorCode >= 12000) + { + dwFlags = FORMAT_MESSAGE_FROM_HMODULE; + lpSource = GetModuleHandleA("winhttp.dll"); // this handle DOES NOT need to be freed + } +#endif + + std::wstring buffer; + buffer.resize(buffer_size); + + const auto result = ::FormatMessageW( + dwFlags, + lpSource, + errorCode, + 0, + &buffer[0], + buffer_size, + NULL); + + if (result == 0) + { + std::ostringstream os; + os << "Unable to get an error message for error code: " << errorCode << "."; + return os.str(); + } + + return utility::conversions::to_utf8string(buffer); +} + +std::error_condition windows_category_impl::default_error_condition(int errorCode) const +{ + // First see if the STL implementation can handle the mapping for common cases. + const std::error_condition errCondition = std::system_category().default_error_condition(errorCode); + const std::string errConditionMsg = errCondition.message(); + if(_stricmp(errConditionMsg.c_str(), "unknown error") != 0) + { + return errCondition; + } + + switch(errorCode) + { +#ifndef __cplusplus_winrt + case ERROR_WINHTTP_TIMEOUT: + return std::errc::timed_out; + case ERROR_WINHTTP_CANNOT_CONNECT: + return std::errc::host_unreachable; + case ERROR_WINHTTP_CONNECTION_ERROR: + return std::errc::connection_aborted; +#endif + case INET_E_RESOURCE_NOT_FOUND: + case INET_E_CANNOT_CONNECT: + return std::errc::host_unreachable; + case INET_E_CONNECTION_TIMEOUT: + return std::errc::timed_out; + case INET_E_DOWNLOAD_FAILURE: + return std::errc::connection_aborted; + default: + break; + } + + return std::error_condition(errorCode, *this); +} + +#else + +const std::error_category & __cdecl linux_category() +{ + // On Linux we are using boost error codes which have the exact same + // mapping and are equivalent with std::generic_category error codes. + return std::generic_category(); +} + +#endif + +} + +#pragma endregion + +#pragma region conversions + +utf16string __cdecl conversions::utf8_to_utf16(const std::string &s) +{ + if(s.empty()) + { + return utf16string(); + } + +#ifdef _MS_WINDOWS + // first find the size + int size = ::MultiByteToWideChar( + CP_UTF8, // convert to utf-8 + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + nullptr, 0); // must be null for utf8 + + if (size == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + + utf16string buffer; + buffer.resize(size); + + // now call again to format the string + const int result = ::MultiByteToWideChar( + CP_UTF8, // convert to utf-8 + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + &buffer[0], size); // must be null for utf8 + + if (result != size) + { + throw utility::details::create_system_error(GetLastError()); + } + + return buffer; +#else + return utf_to_utf(s, stop); +#endif +} + +std::string __cdecl conversions::utf16_to_utf8(const utf16string &w) +{ + if(w.empty()) + { + return std::string(); + } + +#ifdef _MS_WINDOWS + // first find the size + const int size = ::WideCharToMultiByte( + CP_UTF8, // convert to utf-8 +#if _WIN32_WINNT >= _WIN32_WINNT_VISTA + WC_ERR_INVALID_CHARS, // fail if any characters can't be translated +#else + 0, // ERROR_INVALID_FLAGS is not supported in XP, set this dwFlags to 0 +#endif // _WIN32_WINNT >= _WIN32_WINNT_VISTA + w.c_str(), + (int)w.size(), + nullptr, 0, // find the size required + nullptr, nullptr); // must be null for utf8 + + if (size == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + + std::string buffer; + buffer.resize(size); + + // now call again to format the string + const int result = ::WideCharToMultiByte( + CP_UTF8, // convert to utf-8 +#if _WIN32_WINNT >= _WIN32_WINNT_VISTA + WC_ERR_INVALID_CHARS, // fail if any characters can't be translated +#else + 0, // ERROR_INVALID_FLAGS is not supported in XP, set this dwFlags to 0 +#endif // _WIN32_WINNT >= _WIN32_WINNT_VISTA + w.c_str(), + (int)w.size(), + &buffer[0], size, + nullptr, nullptr); // must be null for utf8 + + if (result != size) + { + throw utility::details::create_system_error(GetLastError()); + } + + return buffer; +#else + return utf_to_utf(w, stop); +#endif +} + +utf16string __cdecl conversions::usascii_to_utf16(const std::string &s) +{ + if(s.empty()) + { + return utf16string(); + } + +#ifdef _MS_WINDOWS + int size = ::MultiByteToWideChar( + 20127, // convert from us-ascii + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + nullptr, 0); + + if (size == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + + // this length includes the terminating null + std::wstring buffer; + buffer.resize(size); + + // now call again to format the string + int result = ::MultiByteToWideChar( + 20127, // convert from us-ascii + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + &buffer[0], size); + + if (result != size) + { + throw utility::details::create_system_error(GetLastError()); + } + + return buffer; +#else + return utf_to_utf(to_utf(s, "ascii", stop)); +#endif +} + +utf16string __cdecl conversions::latin1_to_utf16(const std::string &s) +{ + if(s.empty()) + { + return utf16string(); + } + +#ifdef _MS_WINDOWS + int size = ::MultiByteToWideChar( + 28591, // convert from Latin1 + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + nullptr, 0); + + if (size == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + + // this length includes the terminating null + std::wstring buffer; + buffer.resize(size); + + // now call again to format the string + int result = ::MultiByteToWideChar( + 28591, // convert from Latin1 + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + &buffer[0], size); + + if (result != size) + { + throw utility::details::create_system_error(GetLastError()); + } + + return buffer; +#else + return utf_to_utf(to_utf(s, "Latin1", stop)); +#endif +} + +utf16string __cdecl conversions::default_code_page_to_utf16(const std::string &s) +{ + if(s.empty()) + { + return utf16string(); + } + +#ifdef _MS_WINDOWS + // First have to convert to UTF-16. + int size = ::MultiByteToWideChar( + CP_ACP, // convert from Windows system default + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + nullptr, 0); + if (size == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + + // this length includes the terminating null + std::wstring buffer; + buffer.resize(size); + + // now call again to format the string + int result = ::MultiByteToWideChar( + CP_ACP, // convert from Windows system default + MB_ERR_INVALID_CHARS, // fail if any characters can't be translated + s.c_str(), + (int)s.size(), + &buffer[0], size); + if(result == size) + { + return buffer; + } + else + { + throw utility::details::create_system_error(GetLastError()); + } +#else // LINUX + return utf_to_utf(to_utf(s, std::locale(""), stop)); +#endif +} + +utility::string_t __cdecl conversions::to_string_t(utf16string &&s) +{ +#ifdef _UTF16_STRINGS + return std::move(s); +#else + return utf16_to_utf8(std::move(s)); +#endif +} + +utility::string_t __cdecl conversions::to_string_t(std::string &&s) +{ +#ifdef _UTF16_STRINGS + return utf8_to_utf16(std::move(s)); +#else + return std::move(s); +#endif +} + +utility::string_t __cdecl conversions::to_string_t(const utf16string &s) +{ +#ifdef _UTF16_STRINGS + return s; +#else + return utf16_to_utf8(s); +#endif +} + +utility::string_t __cdecl conversions::to_string_t(const std::string &s) +{ +#ifdef _UTF16_STRINGS + return utf8_to_utf16(s); +#else + return s; +#endif +} + +std::string __cdecl conversions::to_utf8string(std::string value) { return std::move(value); } + +std::string __cdecl conversions::to_utf8string(const utf16string &value) { return utf16_to_utf8(value); } + +utf16string __cdecl conversions::to_utf16string(const std::string &value) { return utf8_to_utf16(value); } + +utf16string __cdecl conversions::to_utf16string(utf16string value) { return std::move(value); } + + +#pragma endregion + +#pragma region datetime + +#ifndef WIN32 +datetime datetime::timeval_to_datetime(struct timeval time) +{ + const uint64_t epoch_offset = 11644473600LL; // diff between windows and unix epochs (seconds) + uint64_t result = epoch_offset + time.tv_sec; + result *= _secondTicks; // convert to 10e-7 + result += time.tv_usec; //add microseconds (in 10e-7) + return datetime(result); +} +#endif + +static bool is_digit(utility::char_t c) { return c >= _XPLATSTR('0') && c <= _XPLATSTR('9'); } + +/// +/// Returns the current UTC date and time. +/// +datetime __cdecl datetime::utc_now() +{ +#ifdef _MS_WINDOWS + ULARGE_INTEGER largeInt; + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + + largeInt.LowPart = fileTime.dwLowDateTime; + largeInt.HighPart = fileTime.dwHighDateTime; + + return datetime(largeInt.QuadPart); +#else //LINUX + struct timeval time; + gettimeofday(&time, nullptr); + return timeval_to_datetime(time); +#endif +} + +/// +/// Returns a string representation of the datetime. The string is formatted based on RFC 1123 or ISO 8601 +/// +utility::string_t datetime::to_string(date_format format) const +{ +#ifdef _MS_WINDOWS + int status; + + ULARGE_INTEGER largeInt; + largeInt.QuadPart = m_interval; + + FILETIME ft; + ft.dwHighDateTime = largeInt.HighPart; + ft.dwLowDateTime = largeInt.LowPart; + + SYSTEMTIME systemTime; + if (!FileTimeToSystemTime((const FILETIME *)&ft, &systemTime)) + { + throw utility::details::create_system_error(GetLastError()); + } + + std::wostringstream outStream; + + if ( format == RFC_1123 ) + { +#if _WIN32_WINNT < _WIN32_WINNT_VISTA + TCHAR dateStr[18] = {0}; + status = GetDateFormat(LOCALE_INVARIANT, 0, &systemTime, __TEXT("ddd',' dd MMM yyyy"), dateStr, sizeof(dateStr) / sizeof(TCHAR)); +#else + wchar_t dateStr[18] = {0}; + status = GetDateFormatEx(LOCALE_NAME_INVARIANT, 0, &systemTime, L"ddd',' dd MMM yyyy", dateStr, sizeof(dateStr) / sizeof(wchar_t), NULL); +#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA + if (status == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + +#if _WIN32_WINNT < _WIN32_WINNT_VISTA + TCHAR timeStr[10] = {0}; + status = GetTimeFormat(LOCALE_INVARIANT, TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &systemTime, "HH':'mm':'ss", timeStr, sizeof(timeStr) / sizeof(TCHAR)); +#else + wchar_t timeStr[10] = {0}; + status = GetTimeFormatEx(LOCALE_NAME_INVARIANT, TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &systemTime, L"HH':'mm':'ss", timeStr, sizeof(timeStr) / sizeof(wchar_t)); +#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA + if (status == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + + outStream << dateStr << " " << timeStr << " " << "GMT"; + } + else if ( format == ISO_8601 ) + { + const size_t buffSize = 64; +#if _WIN32_WINNT < _WIN32_WINNT_VISTA + TCHAR dateStr[buffSize] = {0}; + status = GetDateFormat(LOCALE_INVARIANT, 0, &systemTime, "yyyy-MM-dd", dateStr, buffSize); +#else + wchar_t dateStr[buffSize] = {0}; + status = GetDateFormatEx(LOCALE_NAME_INVARIANT, 0, &systemTime, L"yyyy-MM-dd", dateStr, buffSize, NULL); +#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA + if (status == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + +#if _WIN32_WINNT < _WIN32_WINNT_VISTA + TCHAR timeStr[buffSize] = {0}; + status = GetTimeFormat(LOCALE_INVARIANT, TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &systemTime, "HH':'mm':'ss", timeStr, buffSize); +#else + wchar_t timeStr[buffSize] = {0}; + status = GetTimeFormatEx(LOCALE_NAME_INVARIANT, TIME_NOTIMEMARKER | TIME_FORCE24HOURFORMAT, &systemTime, L"HH':'mm':'ss", timeStr, buffSize); +#endif // _WIN32_WINNT < _WIN32_WINNT_VISTA + if (status == 0) + { + throw utility::details::create_system_error(GetLastError()); + } + + outStream << dateStr << "T" << timeStr; + uint64_t frac_sec = largeInt.QuadPart % _secondTicks; + if (frac_sec > 0) + { + // Append fractional second, which is a 7-digit value with no trailing zeros + // This way, '1200' becomes '00012' + char buf[9] = { 0 }; + sprintf_s(buf, sizeof(buf), ".%07ld", (long int)frac_sec); + // trim trailing zeros + for (int i = 7; buf[i] == '0'; i--) buf[i] = '\0'; + outStream << buf; + } + outStream << "Z"; + } + + return outStream.str(); +#else //LINUX + uint64_t input = m_interval; + uint64_t frac_sec = input % _secondTicks; + input /= _secondTicks; // convert to seconds + time_t time = (time_t)input - (time_t)11644473600LL;// diff between windows and unix epochs (seconds) + + struct tm datetime; + gmtime_r(&time, &datetime); + + const int max_dt_length = 64; + char output[max_dt_length+1] = {0}; + + if (format != RFC_1123 && frac_sec > 0) + { + // Append fractional second, which is a 7-digit value with no trailing zeros + // This way, '1200' becomes '00012' + char buf[9] = { 0 }; + snprintf(buf, sizeof(buf), ".%07ld", (long int)frac_sec); + // trim trailing zeros + for (int i = 7; buf[i] == '0'; i--) buf[i] = '\0'; + // format the datetime into a separate buffer + char datetime_str[max_dt_length+1] = {0}; + strftime(datetime_str, sizeof(datetime_str), "%Y-%m-%dT%H:%M:%S", &datetime); + // now print this buffer into the output buffer + snprintf(output, sizeof(output), "%s%sZ", datetime_str, buf); + } + else + { + strftime(output, sizeof(output), + format == RFC_1123 ? "%a, %d %b %Y %H:%M:%S GMT" : "%Y-%m-%dT%H:%M:%SZ", + &datetime); + } + + return std::string(output); +#endif +} + +#ifdef _MS_WINDOWS +bool __cdecl datetime::system_type_to_datetime(void* pvsysTime, uint64_t seconds, datetime * pdt) +{ + SYSTEMTIME* psysTime = (SYSTEMTIME*)pvsysTime; + FILETIME fileTime; + + if (SystemTimeToFileTime(psysTime, &fileTime)) + { + ULARGE_INTEGER largeInt; + largeInt.LowPart = fileTime.dwLowDateTime; + largeInt.HighPart = fileTime.dwHighDateTime; + + // Add hundredths of nanoseconds + largeInt.QuadPart += seconds; + + *pdt = datetime(largeInt.QuadPart); + return true; + } + return false; +} +#endif + +// Take a string that represents a fractional second and return the number of ticks +// This is equivalent to doing atof on the string and multiplying by 10000000, +// but does not lose precision +template +uint64_t timeticks_from_second(StringIterator begin, StringIterator end) +{ + int size = (int)(end - begin); + _ASSERTE(begin[0] == U('.')); + uint64_t ufrac_second = 0; + for (int i = 1; i <= 7; ++i) + { + ufrac_second *= 10; + int add = i < size ? begin[i] - U('0') : 0; + ufrac_second += add; + } + return ufrac_second; +} + +void extract_fractional_second(const utility::string_t& dateString, utility::string_t& resultString, uint64_t& ufrac_second) +{ + resultString = dateString; + // First, the string must be strictly longer than 2 characters, and the trailing character must be 'Z' + if (resultString.size() > 2 && resultString[resultString.size() - 1] == U('Z')) + { + // Second, find the last non-digit by scanning the string backwards + auto last_non_digit = std::find_if_not(resultString.rbegin() + 1, resultString.rend(), is_digit); + if (last_non_digit < resultString.rend() - 1) + { + // Finally, make sure the last non-digit is a dot: + auto last_dot = last_non_digit.base() - 1; + if (*last_dot == U('.')) + { + // Got it! Now extract the fractional second + auto last_before_Z = std::end(resultString) - 1; + ufrac_second = timeticks_from_second(last_dot, last_before_Z); + // And erase it from the string + resultString.erase(last_dot, last_before_Z); + } + } + } +} + +/// +/// Returns a string representation of the datetime. The string is formatted based on RFC 1123 or ISO 8601 +/// +datetime __cdecl datetime::from_string(const utility::string_t& dateString, date_format format) +{ + // avoid floating point math to preserve precision + uint64_t ufrac_second = 0; + +#ifdef _MS_WINDOWS + datetime result; + if ( format == RFC_1123 ) + { + SYSTEMTIME sysTime = {0}; + + std::wstring month(3, L'\0'); + std::wstring unused(3, L'\0'); + + const wchar_t * formatString = L"%3c, %2d %3c %4d %2d:%2d:%2d %3c"; + auto n = swscanf_s(dateString.c_str(), formatString, + unused.data(), unused.size(), + &sysTime.wDay, + month.data(), month.size(), + &sysTime.wYear, + &sysTime.wHour, + &sysTime.wMinute, + &sysTime.wSecond, + unused.data(), unused.size()); + + if (n == 8) + { + std::wstring monthnames[12] = {L"Jan", L"Feb", L"Mar", L"Apr", L"May", L"Jun", L"Jul", L"Aug", L"Sep", L"Oct", L"Nov", L"Dec"}; + auto loc = std::find_if(monthnames, monthnames+12, [&month](const std::wstring& m) { return m == month;}); + + if (loc != monthnames+12) + { + sysTime.wMonth = (short) ((loc - monthnames) + 1); + if (system_type_to_datetime(&sysTime, ufrac_second, &result)) + { + return result; + } + } + } + } + else if ( format == ISO_8601 ) + { + // Unlike FILETIME, SYSTEMTIME does not have enough precision to hold seconds in 100 nanosecond + // increments. Therefore, start with seconds and milliseconds set to 0, then add them separately + + // Try to extract the fractional second from the timestamp + utility::string_t input; + extract_fractional_second(dateString, input, ufrac_second); + { + SYSTEMTIME sysTime = { 0 }; + const wchar_t * formatString = L"%4d-%2d-%2dT%2d:%2d:%2dZ"; + auto n = swscanf_s(input.c_str(), formatString, + &sysTime.wYear, + &sysTime.wMonth, + &sysTime.wDay, + &sysTime.wHour, + &sysTime.wMinute, + &sysTime.wSecond); + + if (n == 3 || n == 6) + { + if (system_type_to_datetime(&sysTime, ufrac_second, &result)) + { + return result; + } + } + } + { + SYSTEMTIME sysTime = {0}; + DWORD date = 0; + + const wchar_t * formatString = L"%8dT%2d:%2d:%2dZ"; + auto n = swscanf_s(input.c_str(), formatString, + &date, + &sysTime.wHour, + &sysTime.wMinute, + &sysTime.wSecond); + + if (n == 1 || n == 4) + { + sysTime.wDay = date % 100; + date /= 100; + sysTime.wMonth = date % 100; + date /= 100; + sysTime.wYear = (WORD)date; + + if (system_type_to_datetime(&sysTime, ufrac_second, &result)) + { + return result; + } + } + } + { + SYSTEMTIME sysTime = {0}; + GetSystemTime(&sysTime); // Fill date portion with today's information + sysTime.wSecond = 0; + sysTime.wMilliseconds = 0; + + const wchar_t * formatString = L"%2d:%2d:%2dZ"; + auto n = swscanf_s(input.c_str(), formatString, + &sysTime.wHour, + &sysTime.wMinute, + &sysTime.wSecond); + + if (n == 3) + { + if (system_type_to_datetime(&sysTime, ufrac_second, &result)) + { + return result; + } + } + } + } + + return datetime(); +#else + std::string input(dateString); + + struct tm output = tm(); + + if ( format == RFC_1123 ) + { + strptime(input.data(), "%a, %d %b %Y %H:%M:%S GMT", &output); + } + else + { + // Try to extract the fractional second from the timestamp + utility::string_t input; + extract_fractional_second(dateString, input, ufrac_second); + + auto result = strptime(input.data(), "%Y-%m-%dT%H:%M:%SZ", &output); + + if ( result == nullptr ) + { + result = strptime(input.data(), "%Y%m%dT%H:%M:%SZ", &output); + } + if ( result == nullptr ) + { + // Fill the date portion with the epoch, + // strptime will do the rest + memset(&output, 0, sizeof(struct tm)); + output.tm_year = 70; + output.tm_mon = 1; + output.tm_mday = 1; + result = strptime(input.data(), "%H:%M:%SZ", &output); + } + if ( result == nullptr ) + { + result = strptime(input.data(), "%Y-%m-%d", &output); + } + if ( result == nullptr ) + { + result = strptime(input.data(), "%Y%m%d", &output); + } + if ( result == nullptr ) + { + return datetime(); + } + } + + time_t time = timegm(&output); + + struct timeval tv = timeval(); + tv.tv_sec = time; + tv.tv_usec = (unsigned int)ufrac_second; + return timeval_to_datetime(tv); +#endif +} +#pragma endregion + +#pragma region "timespan" + +/// +/// Converts a timespan/interval in seconds to xml duration string as specified by +/// http://www.w3.org/TR/xmlschema-2/#duration +/// +utility::string_t __cdecl timespan::seconds_to_xml_duration(utility::seconds durationSecs) +{ + auto numSecs = durationSecs.count(); + + // Find the number of minutes + auto numMins = numSecs / 60; + if (numMins > 0) + { + numSecs = numSecs % 60; + } + + // Hours + auto numHours = numMins / 60; + if (numHours > 0) + { + numMins = numMins % 60; + } + + // Days + auto numDays = numHours / 24; + if (numDays > 0) + { + numHours = numHours % 24; + } + + // The format is: + // PdaysDThoursHminutesMsecondsS + utility::ostringstream_t oss; + + oss << _XPLATSTR("P"); + if (numDays > 0) + { + oss << numDays << _XPLATSTR("D"); + } + + oss << _XPLATSTR("T"); + + if (numHours > 0) + { + oss << numHours << _XPLATSTR("H"); + } + + if (numMins > 0) + { + oss << numMins << _XPLATSTR("M"); + } + + if (numSecs > 0) + { + oss << numSecs << _XPLATSTR("S"); + } + + return oss.str(); +} + +/// +/// Converts an xml duration to timespan/interval in seconds +/// http://www.w3.org/TR/xmlschema-2/#duration +/// +utility::seconds __cdecl timespan::xml_duration_to_seconds(utility::string_t timespanString) +{ + // The format is: + // PnDTnHnMnS + // if n == 0 then the field could be omitted + // The final S could be omitted + + int64_t numSecs = 0; + + utility::istringstream_t is(timespanString); + auto eof = std::char_traits::eof(); + + std::basic_istream::int_type c; + c = is.get(); // P + + while (c != eof) + { + int val = 0; + c = is.get(); + + while (is_digit((utility::char_t)c)) + { + val = val * 10 + (c - L'0'); + c = is.get(); + + if (c == '.') + { + // decimal point is not handled + do { c = is.get(); } while(is_digit((utility::char_t)c)); + } + } + + if (c == L'D') numSecs += val * 24 * 3600; // days + if (c == L'H') numSecs += val * 3600; // Hours + if (c == L'M') numSecs += val * 60; // Minutes + if (c == L'S' || c == eof) + { + numSecs += val; // seconds + break; + } + } + + return utility::seconds(numSecs); +} + +#pragma endregion + +} +} diff --git a/src/common/base64.cpp b/src/common/base64.cpp new file mode 100644 index 0000000..3bab2a6 --- /dev/null +++ b/src/common/base64.cpp @@ -0,0 +1,247 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" + +using namespace odata; +using namespace odata::utility; + +#define _USE_INTERNAL_BASE64_ +std::vector _from_base64(const utility::string_t& str); +utility::string_t _to_base64(const unsigned char *ptr, size_t size); + +std::vector __cdecl conversions::from_base64(const utility::string_t& str) +{ + return _from_base64(str); +} + +utility::string_t __cdecl conversions::to_base64(const std::vector& input) +{ + if (input.size() == 0) + { + // return empty string + return utility::string_t(); + } + + return _to_base64(&input[0], input.size()); +} + +utility::string_t __cdecl conversions::to_base64(uint64_t input) +{ + return _to_base64(reinterpret_cast(&input), sizeof(input)); +} + + +#if defined(_USE_INTERNAL_BASE64_) +static const char* _base64_enctbl = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; +unsigned char _base64_dectbl [] = + { 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63, + 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 254, 255, 255, + 255, 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, 255, 255, 255, 255, 255, + 255, 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, 255, 255, 255, 255, 255 }; + +struct _triple_byte +{ + unsigned char _1_1 : 2; + unsigned char _0 : 6; + unsigned char _2_1 : 4; + unsigned char _1_2 : 4; + unsigned char _3 : 6; + unsigned char _2_2 : 2; +}; + +// +// A note on the implementation of BASE64 encoding and decoding: +// +// This is a fairly basic and naive implementation; there is probably a lot of room for +// performance improvement, as well as for adding options such as support for URI-safe base64, +// ignoring CRLF, relaxed validation rules, etc. The decoder is currently pretty strict. +// + +#ifdef __GNUC__ +// gcc is concerned about the bitfield uses in the code, something we simply need to ignore. +#pragma GCC diagnostic ignored "-Wconversion" +#endif +std::vector _from_base64(const utility::string_t& input) +{ + std::vector result; + + if ( input.empty() ) + return result; + + size_t padding = 0; + + // Validation + { + auto size = input.size(); + + if ( (size % 4) != 0 ) + { + throw std::runtime_error("length of base64 string is not an even multiple of 4"); + } + + for (auto iter = input.begin(); iter != input.end(); ++iter,--size) + { + auto ch = *iter; + if ( ch < 0 || _base64_dectbl[ch] == 255 ) + { + throw std::runtime_error("invalid character found in base64 string"); + } + if ( _base64_dectbl[ch] == 254 ) + { + padding++; + // padding only at the end + if ( size > 2 || (size == 2 && _base64_dectbl[*(iter+1)] != 254) ) + { + throw std::runtime_error("invalid padding character found in base64 string"); + } + } + } + } + + + auto size = input.size(); + const char_t* ptr = &input[0]; + + auto outsz = (size / 4)*3; + outsz -= padding; + + result.resize(outsz); + + size_t idx = 0; + for (; size > 4; ++idx ) + { + unsigned char target[3]; + memset(target, 0, sizeof(target)); + _triple_byte* record = reinterpret_cast<_triple_byte*>(target); + + unsigned char val0 = _base64_dectbl[ptr[0]]; + unsigned char val1 = _base64_dectbl[ptr[1]]; + unsigned char val2 = _base64_dectbl[ptr[2]]; + unsigned char val3 = _base64_dectbl[ptr[3]]; + + record->_0 = val0; + record->_1_1 = val1 >> 4; + result[idx] = target[0]; + + record->_1_2 = val1 & 0xF; + record->_2_1 = val2 >> 2; + result[++idx] = target[1]; + + record->_2_2 = val2 & 0x3; + record->_3 = val3 & 0x3F; + result[++idx] = target[2]; + + ptr += 4; + size -= 4; + } + + // Handle the last four bytes separately, to avoid having the conditional statements + // in all the iterations (a performance issue). + + { + unsigned char target[3]; + memset(target, 0, sizeof(target)); + _triple_byte* record = reinterpret_cast<_triple_byte*>(target); + + unsigned char val0 = _base64_dectbl[ptr[0]]; + unsigned char val1 = _base64_dectbl[ptr[1]]; + unsigned char val2 = _base64_dectbl[ptr[2]]; + unsigned char val3 = _base64_dectbl[ptr[3]]; + + record->_0 = val0; + record->_1_1 = val1 >> 4; + result[idx] = target[0]; + + record->_1_2 = val1 & 0xF; + if ( val2 != 254 ) + { + record->_2_1 = val2 >> 2; + result[++idx] = target[1]; + } + else + { + // There shouldn't be any information (ones) in the unused bits, + if ( record->_1_2 != 0 ) + { + throw std::runtime_error("Invalid end of base64 string"); + } + return result; + } + + record->_2_2 = val2 & 0x3; + if ( val3 != 254 ) + { + record->_3 = val3 & 0x3F; + result[++idx] = target[2]; + } + else + { + // There shouldn't be any information (ones) in the unused bits. + if ( record->_2_2 != 0 ) + { + throw std::runtime_error("Invalid end of base64 string"); + } + return result; + } + } + + return result; +} + +utility::string_t _to_base64(const unsigned char *ptr, size_t size) +{ + utility::string_t result; + + for (; size >= 3; ) + { + const _triple_byte* record = reinterpret_cast(ptr); + unsigned char idx0 = record->_0; + unsigned char idx1 = (record->_1_1 << 4) | record->_1_2; + unsigned char idx2 = (record->_2_1 << 2) | record->_2_2; + unsigned char idx3 = record->_3; + result.push_back(char_t(_base64_enctbl[idx0])); + result.push_back(char_t(_base64_enctbl[idx1])); + result.push_back(char_t(_base64_enctbl[idx2])); + result.push_back(char_t(_base64_enctbl[idx3])); + size -= 3; + ptr += 3; + } + switch(size) + { + case 1: + { + const _triple_byte* record = reinterpret_cast(ptr); + unsigned char idx0 = record->_0; + unsigned char idx1 = (record->_1_1 << 4); + result.push_back(char_t(_base64_enctbl[idx0])); + result.push_back(char_t(_base64_enctbl[idx1])); + result.push_back('='); + result.push_back('='); + break; + } + case 2: + { + const _triple_byte* record = reinterpret_cast(ptr); + unsigned char idx0 = record->_0; + unsigned char idx1 = (record->_1_1 << 4) | record->_1_2; + unsigned char idx2 = (record->_2_1 << 2); + result.push_back(char_t(_base64_enctbl[idx0])); + result.push_back(char_t(_base64_enctbl[idx1])); + result.push_back(char_t(_base64_enctbl[idx2])); + result.push_back('='); + break; + } + } + return result; +} +#endif diff --git a/src/common/json.cpp b/src/common/json.cpp new file mode 100644 index 0000000..28e3462 --- /dev/null +++ b/src/common/json.cpp @@ -0,0 +1,419 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" +#include "odata/common/json.h" + +using namespace odata::utility; + +bool json::details::g_keep_json_object_unsorted = false; +void json::keep_object_element_order(bool keep_order) +{ + json::details::g_keep_json_object_unsorted = keep_order; +} + +odata::utility::ostream_t& odata::utility::json::operator << (utility::ostream_t &os, const odata::utility::json::value &val) +{ + val.serialize(os); + return os; +} + +odata::utility::istream_t& odata::utility::json::operator >> (utility::istream_t &is, json::value &val) +{ + val = json::value::parse(is); + return is; +} + +#pragma region json::value Constructors + +odata::utility::json::value::value() : + m_value(utility::details::make_unique()) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Null) +#endif + { } + +odata::utility::json::value::value(int32_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +odata::utility::json::value::value(uint32_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +odata::utility::json::value::value(int64_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +odata::utility::json::value::value(uint64_t value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +odata::utility::json::value::value(double value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Number) +#endif + { } + +odata::utility::json::value::value(bool value) : + m_value(utility::details::make_unique(value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::Boolean) +#endif + { } + +odata::utility::json::value::value(utility::string_t value) : + m_value(utility::details::make_unique(std::move(value))) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::String) +#endif + { } + +odata::utility::json::value::value(const utility::char_t* value) : + m_value(utility::details::make_unique(utility::string_t(value))) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(value::String) +#endif + { } + +odata::utility::json::value::value(const value &other) : + m_value(other.m_value->_copy_value()) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(other.m_kind) +#endif + { } + +odata::utility::json::value &odata::utility::json::value::operator=(const value &other) +{ + if(this != &other) + { + m_value = std::unique_ptr(other.m_value->_copy_value()); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = other.m_kind; +#endif + } + return *this; +} +odata::utility::json::value::value(value &&other) : + m_value(std::move(other.m_value)) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,m_kind(other.m_kind) +#endif +{} + +odata::utility::json::value &odata::utility::json::value::operator=(odata::utility::json::value &&other) +{ + if(this != &other) + { + m_value.swap(other.m_value); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = other.m_kind; +#endif + } + return *this; +} + +#pragma endregion + + +#pragma region Static Factories + +odata::utility::json::value odata::utility::json::value::null() +{ + return odata::utility::json::value(); +} + +odata::utility::json::value odata::utility::json::value::number(double value) +{ + return odata::utility::json::value(value); +} + +odata::utility::json::value odata::utility::json::value::number(int32_t value) +{ + return odata::utility::json::value(value); +} + +odata::utility::json::value odata::utility::json::value::boolean(bool value) +{ + return odata::utility::json::value(value); +} + +odata::utility::json::value odata::utility::json::value::string(utility::string_t value) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(value)); + return odata::utility::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::String +#endif + ); +} + +#ifdef _MS_WINDOWS +odata::utility::json::value odata::utility::json::value::string(const std::string &value) +{ + std::unique_ptr ptr = utility::details::make_unique(utility::conversions::to_utf16string(value)); + return odata::utility::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::String +#endif + ); +} +#endif + +odata::utility::json::value odata::utility::json::value::object(bool keep_order) +{ + std::unique_ptr ptr = utility::details::make_unique(keep_order); + return odata::utility::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Object +#endif + ); +} + +odata::utility::json::value odata::utility::json::value::object(std::vector> fields, bool keep_order) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(fields), keep_order); + return odata::utility::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Object +#endif + ); +} + +odata::utility::json::value odata::utility::json::value::array() +{ + std::unique_ptr ptr = utility::details::make_unique(); + return odata::utility::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Array +#endif + ); +} + +odata::utility::json::value odata::utility::json::value::array(size_t size) +{ + std::unique_ptr ptr = utility::details::make_unique(size); + return odata::utility::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Array +#endif + ); +} + +odata::utility::json::value odata::utility::json::value::array(std::vector elements) +{ + std::unique_ptr ptr = utility::details::make_unique(std::move(elements)); + return odata::utility::json::value(std::move(ptr) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,value::Array +#endif + ); +} + +#pragma endregion + +odata::utility::json::number odata::utility::json::value::as_number() const +{ + return m_value->as_number(); +} + +double odata::utility::json::value::as_double() const +{ + return m_value->as_double(); +} + +int odata::utility::json::value::as_integer() const +{ + return m_value->as_integer(); +} + +bool odata::utility::json::value::as_bool() const +{ + return m_value->as_bool(); +} + +json::array& odata::utility::json::value::as_array() +{ + return m_value->as_array(); +} + +const json::array& odata::utility::json::value::as_array() const +{ + return m_value->as_array(); +} + +json::object& odata::utility::json::value::as_object() +{ + return m_value->as_object(); +} + +const json::object& odata::utility::json::value::as_object() const +{ + return m_value->as_object(); +} + +bool odata::utility::json::number::is_int32() const +{ + switch (m_type) + { + case signed_type : return m_intval >= std::numeric_limits::min() && m_intval <= std::numeric_limits::max(); + case unsigned_type : return m_uintval <= std::numeric_limits::max(); + case double_type : + default : + return false; + } +} + +bool odata::utility::json::number::is_uint32() const +{ + switch (m_type) + { + case signed_type : return m_intval >= 0 && m_intval <= std::numeric_limits::max(); + case unsigned_type : return m_uintval <= std::numeric_limits::max(); + case double_type : + default : + return false; + } +} + +bool odata::utility::json::number::is_int64() const +{ + switch (m_type) + { + case signed_type : return true; + case unsigned_type : return m_uintval <= static_cast(std::numeric_limits::max()); + case double_type : + default : + return false; + } +} + +bool odata::utility::json::details::_String::has_escape_chars(const _String &str) +{ + static const auto escapes = U("\"\\\b\f\r\n\t"); + return str.m_string.find_first_of(escapes) != utility::string_t::npos; +} + +odata::utility::json::details::_Object::_Object(const _Object& other) : + m_object(other.m_object.m_elements, other.m_object.m_keep_order), odata::utility::json::details::_Value(other) {} + +odata::utility::json::value::value_type json::value::type() const { return m_value->type(); } + +bool json::value::is_integer() const +{ + if(!is_number()) + { + return false; + } + return m_value->is_integer(); +} + +bool json::value::is_double() const +{ + if(!is_number()) + { + return false; + } + return m_value->is_double(); +} + +json::value& odata::utility::json::details::_Object::index(const utility::string_t &key) +{ + return m_object[key]; +} + +bool odata::utility::json::details::_Object::has_field(const utility::string_t &key) const +{ + return m_object.find(key) != m_object.end(); +} + +odata::utility::string_t json::value::to_string() const { return m_value->to_string(); } + +bool json::value::operator==(const json::value &other) const +{ + if (this->m_value.get() == other.m_value.get()) + return true; + if (this->type() != other.type()) + return false; + + switch(this->type()) + { + case Null: + return true; + case Number: + return this->as_number() == other.as_number(); + case Boolean: + return this->as_bool() == other.as_bool(); + case String: + return this->as_string() == other.as_string(); + case Object: + return static_cast(this->m_value.get())->is_equal(static_cast(other.m_value.get())); + case Array: + return static_cast(this->m_value.get())->is_equal(static_cast(other.m_value.get())); + } + UNREACHABLE; +} + +// at() overloads +odata::utility::json::value& odata::utility::json::value::at(size_t index) +{ + return this->as_array().at(index); +} + +const odata::utility::json::value& odata::utility::json::value::at(size_t index) const +{ + return this->as_array().at(index); +} + +odata::utility::json::value& odata::utility::json::value::at(const utility::string_t& key) +{ + return this->as_object().at(key); +} + +const odata::utility::json::value& odata::utility::json::value::at(const utility::string_t& key) const +{ + return this->as_object().at(key); +} + + +odata::utility::json::value& odata::utility::json::value::operator [] (const utility::string_t &key) +{ + if ( this->is_null() ) + { + m_value.reset(new odata::utility::json::details::_Object(details::g_keep_json_object_unsorted)); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = value::Object; +#endif + } + return m_value->index(key); +} + +odata::utility::json::value& odata::utility::json::value::operator[](size_t index) +{ + if ( this->is_null() ) + { + m_value.reset(new odata::utility::json::details::_Array()); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + m_kind = value::Array; +#endif + } + return m_value->index(index); +} diff --git a/src/common/json_parsing.cpp b/src/common/json_parsing.cpp new file mode 100644 index 0000000..97ee850 --- /dev/null +++ b/src/common/json_parsing.cpp @@ -0,0 +1,1130 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" +#include "odata/common/json.h" +#include + +#pragma warning(disable : 4127) // allow expressions like while(true) pass +using namespace odata; +using namespace odata::utility; +using namespace odata::utility::json; +using namespace odata::utility::conversions; + +int _hexval [] = { -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, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 10, 11, 12, 13, 14, 15, -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, 10, 11, 12, 13, 14, 15, -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 }; + +namespace odata { namespace utility { +namespace json +{ +namespace details +{ + +// +// JSON Parsing +// + +template +#ifdef _MS_WINDOWS + __declspec(noreturn) +#else + __attribute__((noreturn)) +#endif +void CreateError(const Token &tk, const utility::string_t &message) +{ + utility::ostringstream_t os; + os << _XPLATSTR("* Line ") << tk.start.m_line << _XPLATSTR(", Column ") << tk.start.m_column << _XPLATSTR(" Syntax error: ") << message; + throw odata::utility::json::json_exception(os.str().c_str()); +} + + +template +class JSON_Parser +{ +public: + JSON_Parser() + : m_currentLine(1), + m_eof(std::char_traits::eof()), + m_currentColumn(1), + m_currentParsingDepth(0) + { } + + struct Location + { + size_t m_line; + size_t m_column; + }; + + struct Token + { + enum Kind + { + TKN_EOF, + + TKN_OpenBrace, + TKN_CloseBrace, + TKN_OpenBracket, + TKN_CloseBracket, + TKN_Comma, + TKN_Colon, + TKN_StringLiteral, + TKN_NumberLiteral, + TKN_IntegerLiteral, + TKN_BooleanLiteral, + TKN_NullLiteral, + TKN_Comment + }; + + Token() : kind(TKN_EOF) {} + + Kind kind; + std::basic_string string_val; + + typename JSON_Parser::Location start; + + union + { + double double_val; + int64_t int64_val; + uint64_t uint64_val; + bool boolean_val; + bool has_unescape_symbol; + }; + + bool signed_number; + }; + + void GetNextToken(Token &); + + odata::utility::json::value ParseValue(typename JSON_Parser::Token &first) + { + auto _value = _ParseValue(first); +#ifdef ENABLE_JSON_VALUE_VISUALIZER + auto type = _value->type(); +#endif + return odata::utility::json::value(std::move(_value) +#ifdef ENABLE_JSON_VALUE_VISUALIZER + ,type +#endif + ); + } + +protected: + virtual CharType NextCharacter() = 0; + virtual CharType PeekCharacter() = 0; + + virtual bool CompleteComment(Token &token); + virtual bool CompleteStringLiteral(Token &token); + bool handle_unescape_char(Token &token); + +private: + + bool CompleteNumberLiteral(CharType first, Token &token); + bool ParseInt64(CharType first, uint64_t& value); + bool CompleteKeywordTrue(Token &token); + bool CompleteKeywordFalse(Token &token); + bool CompleteKeywordNull(Token &token); + std::unique_ptr _ParseValue(typename JSON_Parser::Token &first); + std::unique_ptr _ParseObject(typename JSON_Parser::Token &tkn); + std::unique_ptr _ParseArray(typename JSON_Parser::Token &tkn); + + JSON_Parser& operator=(const JSON_Parser&); + + CharType EatWhitespace(); + + void CreateToken(typename JSON_Parser::Token& tk, typename Token::Kind kind, Location &start) + { + tk.kind = kind; + tk.start = start; + tk.string_val.clear(); + } + + void CreateToken(typename JSON_Parser::Token& tk, typename Token::Kind kind) + { + tk.kind = kind; + tk.start.m_line = m_currentLine; + tk.start.m_column = m_currentColumn; + tk.string_val.clear(); + } + +protected: + + size_t m_currentLine; + size_t m_currentColumn; + size_t m_currentParsingDepth; +#ifndef __APPLE__ + static const size_t maxParsingDepth = 128; +#else + static const size_t maxParsingDepth = 32; +#endif + const typename std::char_traits::int_type m_eof; +}; + +template +class JSON_StreamParser : public JSON_Parser + { +public: + JSON_StreamParser(std::basic_istream &stream) + : m_streambuf(stream.rdbuf()) + { + } + +protected: + + virtual CharType NextCharacter(); + virtual CharType PeekCharacter(); + +private: + typename std::basic_streambuf>* m_streambuf; +}; + +template +class JSON_StringParser : public JSON_Parser +{ +public: + JSON_StringParser(const std::basic_string& string) + : m_position(&string[0]) + { + m_startpos = m_position; + m_endpos = m_position+string.size(); + } + +protected: + + virtual CharType NextCharacter(); + virtual CharType PeekCharacter(); + + virtual bool CompleteComment(typename JSON_Parser::Token &token); + virtual bool CompleteStringLiteral(typename JSON_Parser::Token &token); + +private: + bool finish_parsing_string_with_unescape_char(typename JSON_Parser::Token &token); + const CharType* m_position; + const CharType* m_startpos; + const CharType* m_endpos; +}; + + +template +CharType JSON_StreamParser::NextCharacter() +{ + CharType ch = (CharType) m_streambuf->sbumpc(); + + if (ch == '\n') + { + this->m_currentLine += 1; + this->m_currentColumn = 0; + } + else + { + this->m_currentColumn += 1; + } + + return (CharType)ch; +} + +template +CharType JSON_StreamParser::PeekCharacter() +{ + return (CharType)m_streambuf->sgetc(); +} + +template +CharType JSON_StringParser::NextCharacter() +{ + if (m_position == m_endpos) + return (CharType)this->m_eof; + + CharType ch = *m_position; + m_position += 1; + + if ( ch == '\n' ) + { + this->m_currentLine += 1; + this->m_currentColumn = 0; + } + else + { + this->m_currentColumn += 1; + } + + return (CharType)ch; +} + +template +CharType JSON_StringParser::PeekCharacter() +{ + if ( m_position == m_endpos ) return (CharType)this->m_eof; + + return (CharType)*m_position; +} + +// +// Consume whitespace characters and return the first non-space character or EOF +// +template +CharType JSON_Parser::EatWhitespace() +{ + CharType ch = NextCharacter(); + + while ( ch != this->m_eof && iswspace((int)ch) ) + { + ch = NextCharacter(); + } + + return ch; +} + +template +bool JSON_Parser::CompleteKeywordTrue(Token &token) +{ + if (NextCharacter() != 'r') + return false; + if (NextCharacter() != 'u') + return false; + if (NextCharacter() != 'e') + return false; + token.kind = Token::TKN_BooleanLiteral; + token.boolean_val = true; + return true; +} + +template +bool JSON_Parser::CompleteKeywordFalse(Token &token) +{ + if (NextCharacter() != 'a') + return false; + if (NextCharacter() != 'l') + return false; + if (NextCharacter() != 's') + return false; + if (NextCharacter() != 'e') + return false; + token.kind = Token::TKN_BooleanLiteral; + token.boolean_val = false; + return true; +} + +template +bool JSON_Parser::CompleteKeywordNull(Token &token) +{ + if (NextCharacter() != 'u') + return false; + if (NextCharacter() != 'l') + return false; + if (NextCharacter() != 'l') + return false; + token.kind = Token::TKN_NullLiteral; + return true; +} + +// Returns false only on overflow +template +inline bool JSON_Parser::ParseInt64(CharType first, uint64_t& value) +{ + value = first - '0'; + CharType ch = PeekCharacter(); + while (ch >= '0' && ch <= '9') + { + int next_digit = ch - '0'; + if (value > (ULLONG_MAX / 10) || (value == ULLONG_MAX/10 && next_digit > ULLONG_MAX%10)) + return false; + + NextCharacter(); + + value *= 10; + value += next_digit; + ch = PeekCharacter(); + } + return true; +} + +// This namespace hides the x-plat helper functions +namespace +{ +#ifdef _MS_WINDOWS + static int print_llu(char* ptr, size_t n, uint64_t val64) + { + return _snprintf_s(ptr, n, _TRUNCATE, "%I64u", val64); + } + + static int print_llu(wchar_t* ptr, size_t n, uint64_t val64) + { + return _snwprintf_s(ptr, n, _TRUNCATE, L"%I64u", val64); + } +#else + static int print_llu(char* ptr, size_t n, unsigned long long val64) + { + return snprintf(ptr, n, "%llu", val64); + } + static int print_llu(char* ptr, size_t n, unsigned long val64) + { + return snprintf(ptr, n, "%lu", val64); + } +#endif + + static double anystod(const char* str) { return strtod(str, nullptr); } + static double anystod(const wchar_t* str) { return wcstod(str, nullptr); } +} + +template +bool JSON_Parser::CompleteNumberLiteral(CharType first, Token &token) +{ + bool minus_sign; + + if (first == '-') + { + minus_sign = true; + + first = NextCharacter(); + } + else + { + minus_sign = false; + } + + if (first < '0' || first > '9') + return false; + + CharType ch = PeekCharacter(); + + //Check for two (or more) zeros at the begining + if (first == '0' && ch == '0') + return false; + + // Parse the number assuming its integer + uint64_t val64; + bool complete = ParseInt64(first, val64); + + ch = PeekCharacter(); + if (complete && ch!='.' && ch!='E' && ch!='e') + { + if (minus_sign) + { + if (val64 > static_cast(1) << 63 ) + { + // It is negative and cannot be represented in int64, so we resort to double + token.double_val = 0 - static_cast(val64); + token.signed_number = true; + token.kind = JSON_Parser::Token::TKN_NumberLiteral; + return true; + } + + // It is negative, but fits into int64 + token.int64_val = 0 - static_cast(val64); + token.kind = JSON_Parser::Token::TKN_IntegerLiteral; + token.signed_number = true; + return true; + } + + // It is positive so we use unsigned int64 + token.uint64_val = val64; + token.kind = JSON_Parser::Token::TKN_IntegerLiteral; + token.signed_number = false; + return true; + } + + // Magic number 5 leaves room for decimal point, null terminator, etc (in most cases) + ::std::vector buf(::std::numeric_limits::digits10 + 5); + int count = print_llu(buf.data(), buf.size(), val64); + _ASSERTE(count >= 0); + _ASSERTE((size_t)count < buf.size()); + // Resize to cut off the null terminator + buf.resize(count); + + bool decimal = false; + + while (ch != this->m_eof) + { + // Digit encountered? + if (ch >= '0' && ch <= '9') + { + buf.push_back(ch); + NextCharacter(); + ch = PeekCharacter(); + } + + // Decimal dot? + else if (ch == '.') + { + if (decimal) + return false; + + decimal = true; + buf.push_back(ch); + + NextCharacter(); + ch = PeekCharacter(); + + // Check that the following char is a digit + if (ch < '0' || ch > '9') + return false; + + buf.push_back(ch); + NextCharacter(); + ch = PeekCharacter(); + } + + // Exponent? + else if (ch == 'E' || ch == 'e') + { + buf.push_back(ch); + NextCharacter(); + ch = PeekCharacter(); + + // Check for the exponent sign + if (ch == '+') + { + buf.push_back(ch); + NextCharacter(); + ch = PeekCharacter(); + } + else if (ch == '-') + { + buf.push_back(ch); + NextCharacter(); + ch = PeekCharacter(); + } + + // First number of the exponent + if (ch >= '0' && ch <= '9') + { + buf.push_back(ch); + NextCharacter(); + ch = PeekCharacter(); + } + else return false; + + // The rest of the exponent + while (ch >= '0' && ch <= '9') + { + buf.push_back(ch); + NextCharacter(); + ch = PeekCharacter(); + } + + // The peeked character is not a number, so we can break from the loop and construct the number + break; + } + else + { + // Not expected number character? + break; + } + }; + + buf.push_back('\0'); + token.double_val = anystod(buf.data()); + if (minus_sign) + { + token.double_val = -token.double_val; + } + token.kind = (JSON_Parser::Token::TKN_NumberLiteral); + + return true; +} + +template +bool JSON_Parser::CompleteComment(Token &token) +{ + // We already found a '/' character as the first of a token -- what kind of comment is it? + + CharType ch = NextCharacter(); + + if ( ch == this->m_eof || (ch != '/' && ch != '*') ) + return false; + + if ( ch == '/' ) + { + // Line comment -- look for a newline or EOF to terminate. + + ch = NextCharacter(); + + while ( ch != this->m_eof && ch != '\n') + { + ch = NextCharacter(); + } + } + else + { + // Block comment -- look for a terminating "*/" sequence. + + ch = NextCharacter(); + + while ( true ) + { + if ( ch == this->m_eof ) + return false; + + if ( ch == '*' ) + { + CharType ch1 = PeekCharacter(); + + if ( ch1 == this->m_eof ) + return false; + + if ( ch1 == '/' ) + { + // Consume the character + NextCharacter(); + break; + } + + ch = ch1; + } + + ch = NextCharacter(); + } + } + + token.kind = Token::TKN_Comment; + + return true; +} + +template +bool JSON_StringParser::CompleteComment(typename JSON_Parser::Token &token) +{ + // This function is specialized for the string parser, since we can be slightly more + // efficient in copying data from the input to the token: do a memcpy() rather than + // one character at a time. + + CharType ch = JSON_StringParser::NextCharacter(); + + if ( ch == this->m_eof || (ch != '/' && ch != '*') ) + return false; + + if ( ch == '/' ) + { + // Line comment -- look for a newline or EOF to terminate. + + ch = JSON_StringParser::NextCharacter(); + + while ( ch != this->m_eof && ch != '\n') + { + ch = JSON_StringParser::NextCharacter(); + } + } + else + { + // Block comment -- look for a terminating "*/" sequence. + + ch = JSON_StringParser::NextCharacter(); + + while ( true ) + { + if ( ch == this->m_eof ) + return false; + + if ( ch == '*' ) + { + ch = JSON_StringParser::PeekCharacter(); + + if ( ch == this->m_eof ) + return false; + + if ( ch == '/' ) + { + // Consume the character + JSON_StringParser::NextCharacter(); + break; + } + + } + + ch = JSON_StringParser::NextCharacter(); + } + } + + token.kind = JSON_Parser::Token::TKN_Comment; + + return true; +} + +template +inline bool JSON_Parser::handle_unescape_char(Token &token) +{ + // This function converts unescape character pairs (e.g. "\t") into their ASCII or UNICODE representations (e.g. tab sign) + // Also it handles \u + 4 hexadecimal digits + CharType ch = NextCharacter(); + switch (ch) + { + case '\"': + token.string_val.push_back('\"'); + return true; + case '\\': + token.string_val.push_back('\\'); + return true; + case '/': + token.string_val.push_back('/'); + return true; + case 'b': + token.string_val.push_back('\b'); + return true; + case 'f': + token.string_val.push_back('\f'); + return true; + case 'r': + token.string_val.push_back('\r'); + return true; + case 'n': + token.string_val.push_back('\n'); + return true; + case 't': + token.string_val.push_back('\t'); + return true; + case 'u': + { + // A four-hexdigit unicode character + int decoded = 0; + for (int i = 0; i < 4; ++i) + { + ch = NextCharacter(); + if (!isxdigit((unsigned char) (ch))) + return false; + + int val = _hexval[ch]; + _ASSERTE(val != -1); + + // Add the input char to the decoded number + decoded |= (val << (4 * (3 - i))); + } + + // Construct the character based on the decoded number + ch = static_cast(decoded & 0xFFFF); + token.string_val.push_back(ch); + return true; + } + default: + return false; + } +} + +template +bool JSON_Parser::CompleteStringLiteral(Token &token) +{ + CharType ch = NextCharacter(); + while ( ch != '"' ) + { + if ( ch == '\\' ) + { + handle_unescape_char(token); + } + else if (ch >= CharType(0x0) && ch < CharType(0x20)) + { + return false; + } + else + { + if (ch == this->m_eof) + return false; + + token.string_val.push_back(ch); + } + ch = NextCharacter(); + } + + if ( ch == '"' ) + { + token.kind = Token::TKN_StringLiteral; + } + else + { + return false; + } + + return true; +} + +template +bool JSON_StringParser::CompleteStringLiteral(typename JSON_Parser::Token &token) +{ + // This function is specialized for the string parser, since we can be slightly more + // efficient in copying data from the input to the token: do a memcpy() rather than + // one character at a time. + + auto start = m_position; + token.has_unescape_symbol = false; + + CharType ch = JSON_StringParser::NextCharacter(); + + while (ch != '"') + { + if (ch == this->m_eof) + return false; + + if (ch == '\\') + { + token.string_val.resize(m_position - start - 1); + if (token.string_val.size() > 0) + memcpy(&token.string_val[0], start, (m_position - start - 1)*sizeof(CharType)); + + token.has_unescape_symbol = true; + + return finish_parsing_string_with_unescape_char(token); + } + else if (ch >= CharType(0x0) && ch < CharType(0x20)) + { + return false; + } + + ch = JSON_StringParser::NextCharacter(); + } + + token.string_val.resize(m_position - start - 1); + if (token.string_val.size() > 0) + memcpy(&token.string_val[0], start, (m_position - start - 1)*sizeof(CharType)); + + token.kind = JSON_Parser::Token::TKN_StringLiteral; + + return true; +} + +template +bool JSON_StringParser::finish_parsing_string_with_unescape_char(typename JSON_Parser::Token &token) +{ + // This function handles parsing the string when an unescape character is encountered. + // It is called once the part before the unescape char is copied to the token.string_val string + + CharType ch; + + if (!JSON_StringParser::handle_unescape_char(token)) + return false; + + while ((ch = JSON_StringParser::NextCharacter()) != '"') + { + if (ch == '\\') + { + if (!JSON_StringParser::handle_unescape_char(token)) + return false; + } + else + { + if (ch == this->m_eof) + return false; + + token.string_val.push_back(ch); + } + } + + token.kind = JSON_StringParser::Token::TKN_StringLiteral; + + return true; +} + +template +void JSON_Parser::GetNextToken(typename JSON_Parser::Token& result) +{ +try_again: + CharType ch = EatWhitespace(); + + CreateToken(result, Token::TKN_EOF); + + if (ch == this->m_eof) return; + + switch (ch) + { + case '{': + case '[': + { + if(++m_currentParsingDepth > JSON_Parser::maxParsingDepth) + { + CreateError(result, _XPLATSTR("Nesting too deep!")); + break; + } + + typename JSON_Parser::Token::Kind tk = ch == '{' ? Token::TKN_OpenBrace : Token::TKN_OpenBracket; + CreateToken(result, tk, result.start); + break; + } + case '}': + case ']': + { + if((signed int)(--m_currentParsingDepth) < 0) + { + CreateError(result, _XPLATSTR("Mismatched braces!")); + break; + } + + typename JSON_Parser::Token::Kind tk = ch == '}' ? Token::TKN_CloseBrace : Token::TKN_CloseBracket; + CreateToken(result, tk, result.start); + break; + } + case ',': + CreateToken(result, Token::TKN_Comma, result.start); + break; + + case ':': + CreateToken(result, Token::TKN_Colon, result.start); + break; + + case 't': + if (!CompleteKeywordTrue(result)) CreateError(result, _XPLATSTR("Malformed literal")); + break; + case 'f': + if (!CompleteKeywordFalse(result)) CreateError(result, _XPLATSTR("Malformed literal")); + break; + case 'n': + if (!CompleteKeywordNull(result)) CreateError(result, _XPLATSTR("Malformed literal")); + break; + case '/': + if ( !CompleteComment(result) ) CreateError(result, _XPLATSTR("Malformed comment")); + // For now, we're ignoring comments. + goto try_again; + case '"': + if ( !CompleteStringLiteral(result) ) CreateError(result, _XPLATSTR("Malformed string literal")); + break; + + case '-': + case '0': + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + if ( !CompleteNumberLiteral(ch, result) ) CreateError(result, _XPLATSTR("Malformed numeric literal")); + break; + default: + CreateError(result, _XPLATSTR("Malformed token")); + break; + } +} + +template +std::unique_ptr JSON_Parser::_ParseObject(typename JSON_Parser::Token &tkn) +{ + GetNextToken(tkn); + + auto obj = utility::details::make_unique(g_keep_json_object_unsorted); + auto& elems = obj->m_object.m_elements; + + if ( tkn.kind != JSON_Parser::Token::TKN_CloseBrace ) + { + while ( true ) + { + // State 1: New field or end of object, looking for field name or closing brace + + std::basic_string fieldName; + + switch ( tkn.kind ) + { + case JSON_Parser::Token::TKN_StringLiteral: + fieldName = std::move(tkn.string_val); + break; + default: + goto error; + } + + GetNextToken(tkn); + + // State 2: Looking for a colon. + + if ( tkn.kind != JSON_Parser::Token::TKN_Colon ) goto done; + + GetNextToken(tkn); + + // State 3: Looking for an expression. +#ifdef ENABLE_JSON_VALUE_VISUALIZER + auto fieldValue = _ParseValue(tkn); + auto type = fieldValue->type(); + + elems.emplace_back(utility::conversions::to_string_t(std::move(fieldName)), json::value(std::move(fieldValue), type)); +#else + elems.emplace_back(utility::conversions::to_string_t(std::move(fieldName)), json::value(_ParseValue(tkn))); +#endif + + // State 4: Looking for a comma or a closing brace + + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_Comma: + GetNextToken(tkn); + break; + case JSON_Parser::Token::TKN_CloseBrace: + goto done; + default: + goto error; + } + } + } + +done: + + GetNextToken(tkn); + + if (!g_keep_json_object_unsorted) { + ::std::sort(elems.begin(), elems.end(), json::object::compare_pairs); + } + + return obj; + +error: + + CreateError(tkn, _XPLATSTR("Malformed object literal")); +} + +template +std::unique_ptr JSON_Parser::_ParseArray(typename JSON_Parser::Token &tkn) +{ + GetNextToken(tkn); + + auto result = utility::details::make_unique(); + + if ( tkn.kind != JSON_Parser::Token::TKN_CloseBracket ) + { + while ( true ) + { + // State 1: Looking for an expression. + result->m_array.m_elements.emplace_back(ParseValue(tkn)); + + // State 4: Looking for a comma or a closing bracket + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_Comma: + GetNextToken(tkn); + break; + case JSON_Parser::Token::TKN_CloseBracket: + GetNextToken(tkn); + + return result; + default: + CreateError(tkn, _XPLATSTR("Malformed array literal")); + } + } + } + + GetNextToken(tkn); + + return result; +} + +template +std::unique_ptr JSON_Parser::_ParseValue(typename JSON_Parser::Token &tkn) +{ + switch (tkn.kind) + { + case JSON_Parser::Token::TKN_OpenBrace: + return _ParseObject(tkn); + + case JSON_Parser::Token::TKN_OpenBracket: + return _ParseArray(tkn); + + case JSON_Parser::Token::TKN_StringLiteral: + { + auto value = utility::details::make_unique(std::move(tkn.string_val), tkn.has_unescape_symbol); + GetNextToken(tkn); + return std::move(value); + } + + case JSON_Parser::Token::TKN_IntegerLiteral: + { + std::unique_ptr value; + if (tkn.signed_number) + value = utility::details::make_unique(tkn.int64_val); + else + value = utility::details::make_unique(tkn.uint64_val); + + GetNextToken(tkn); + return std::move(value); + } + + case JSON_Parser::Token::TKN_NumberLiteral: + { + auto value = utility::details::make_unique(tkn.double_val); + GetNextToken(tkn); + return std::move(value); + } + case JSON_Parser::Token::TKN_BooleanLiteral: + { + auto value = utility::details::make_unique(tkn.boolean_val); + GetNextToken(tkn); + return std::move(value); + } + case JSON_Parser::Token::TKN_NullLiteral: + { + GetNextToken(tkn); + return utility::details::make_unique(); + } + + default: + CreateError(tkn, _XPLATSTR("Unexpected token")); + } +} + +}}}} + +static odata::utility::json::value _parse_stream(odata::utility::istream_t &stream) +{ + odata::utility::json::details::JSON_StreamParser parser(stream); + + odata::utility::json::details::JSON_Parser::Token tkn; + parser.GetNextToken(tkn); + + auto value = parser.ParseValue(tkn); + if ( tkn.kind != odata::utility::json::details::JSON_Parser::Token::TKN_EOF ) + { + odata::utility::json::details::CreateError(tkn, _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +#ifdef _MS_WINDOWS +static odata::utility::json::value _parse_narrow_stream(std::istream &stream) +{ + odata::utility::json::details::JSON_StreamParser parser(stream); + + odata::utility::json::details::JSON_StreamParser::Token tkn; + parser.GetNextToken(tkn); + + auto value = parser.ParseValue(tkn); + + if ( tkn.kind != odata::utility::json::details::JSON_Parser::Token::TKN_EOF ) + { + odata::utility::json::details::CreateError(tkn, _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} +#endif + +odata::utility::json::value odata::utility::json::value::parse(const utility::string_t& str) +{ + odata::utility::json::details::JSON_StringParser parser(str); + + odata::utility::json::details::JSON_Parser::Token tkn; + parser.GetNextToken(tkn); + + auto value = parser.ParseValue(tkn); + + if (tkn.kind != odata::utility::json::details::JSON_Parser::Token::TKN_EOF) + { + odata::utility::json::details::CreateError(tkn, _XPLATSTR("Left-over characters in stream after parsing a JSON value")); + } + return value; +} + +odata::utility::json::value odata::utility::json::value::parse(utility::istream_t &stream) +{ + return _parse_stream(stream); +} + +#ifdef _MS_WINDOWS +odata::utility::json::value odata::utility::json::value::parse(std::istream& stream) +{ + return _parse_narrow_stream(stream); +} +#endif diff --git a/src/common/json_serialization.cpp b/src/common/json_serialization.cpp new file mode 100644 index 0000000..6ae2293 --- /dev/null +++ b/src/common/json_serialization.cpp @@ -0,0 +1,218 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" +#include "odata/common/json.h" + +#pragma warning(disable : 4127) // allow expressions like while(true) pass +using namespace odata::utility; +using namespace odata::utility::json; +using namespace odata::utility::conversions; + + +// +// JSON Serialization +// + +#ifdef _MS_WINDOWS +void odata::utility::json::value::serialize(std::ostream& stream) const +{ + // This has better performance than writing directly to stream. + std::string str; + m_value->serialize_impl(str); + stream << str; +} +void odata::utility::json::value::format(std::basic_string &string) const +{ + m_value->format(string); +} +#endif + +void odata::utility::json::value::serialize(utility::ostream_t &stream) const +{ + // This has better performance than writing directly to stream. + utility::string_t str; + m_value->serialize_impl(str); + stream << str; +} + +void odata::utility::json::value::format(std::basic_string& string) const +{ + m_value->format(string); +} + +template +void odata::utility::json::details::append_escape_string(std::basic_string& str, const std::basic_string& escaped) +{ + for (auto iter = escaped.begin(); iter != escaped.end(); ++iter) + { + switch (*iter) + { + case '\"': + str += '\\'; + str += '\"'; + break; + case '\\': + str += '\\'; + str += '\\'; + break; + case '\b': + str += '\\'; + str += 'b'; + break; + case '\f': + str += '\\'; + str += 'f'; + break; + case '\r': + str += '\\'; + str += 'r'; + break; + case '\n': + str += '\\'; + str += 'n'; + break; + case '\t': + str += '\\'; + str += 't'; + break; + default: + str += *iter; + } + } +} + +void odata::utility::json::details::format_string(const utility::string_t& key, utility::string_t& str) +{ + str.push_back('"'); + append_escape_string(str, key); + str.push_back('"'); +} + +#ifdef _MS_WINDOWS +void odata::utility::json::details::format_string(const utility::string_t& key, std::string& str) +{ + str.push_back('"'); + append_escape_string(str, utility::conversions::to_utf8string(key)); + str.push_back('"'); +} +#endif + +void odata::utility::json::details::_String::format(std::basic_string& str) const +{ + str.push_back('"'); + + if(m_has_escape_char) + { + append_escape_string(str, utility::conversions::to_utf8string(m_string)); + } + else + { + str.append(utility::conversions::to_utf8string(m_string)); + } + + str.push_back('"'); +} + +void odata::utility::json::details::_Number::format(std::basic_string& stream) const +{ + if(m_number.m_type != number::type::double_type) + { +#ifdef _MS_WINDOWS + // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator. + const size_t tempSize = std::numeric_limits::digits10 + 3; + char tempBuffer[tempSize]; + + // This can be improved performance-wise if we implement our own routine + if (m_number.m_type == number::type::signed_type) + _i64toa_s(m_number.m_intval, tempBuffer, tempSize, 10); + else + _ui64toa_s(m_number.m_uintval, tempBuffer, tempSize, 10); + + const auto numChars = strnlen_s(tempBuffer, tempSize); + stream.append(tempBuffer, numChars); +#else + std::stringstream ss; + if (m_number.m_type == number::type::signed_type) + ss << m_number.m_intval; + else + ss << m_number.m_uintval; + + stream.append(ss.str()); +#endif + } + else + { + // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null terminator + const size_t tempSize = std::numeric_limits::digits10 + 10; + char tempBuffer[tempSize]; +#ifdef _MS_WINDOWS + const auto numChars = sprintf_s(tempBuffer, tempSize, "%.*g", std::numeric_limits::digits10 + 2, m_number.m_value); +#else + const auto numChars = std::snprintf(tempBuffer, tempSize, "%.*g", std::numeric_limits::digits10 + 2, m_number.m_value); +#endif + stream.append(tempBuffer, numChars); + } +} + +#ifdef _MS_WINDOWS + +void odata::utility::json::details::_String::format(std::basic_string& str) const +{ + str.push_back(L'"'); + + if(m_has_escape_char) + { + append_escape_string(str, m_string); + } + else + { + str.append(m_string); + } + + str.push_back(L'"'); +} + +void odata::utility::json::details::_Number::format(std::basic_string& stream) const +{ + if(m_number.m_type != number::type::double_type) + { + // #digits + 1 to avoid loss + 1 for the sign + 1 for null terminator. + const size_t tempSize = std::numeric_limits::digits10 + 3; + wchar_t tempBuffer[tempSize]; + + if (m_number.m_type == number::type::signed_type) + _i64tow_s(m_number.m_intval, tempBuffer, tempSize, 10); + else + _ui64tow_s(m_number.m_uintval, tempBuffer, tempSize, 10); + + stream.append(tempBuffer, wcsnlen_s(tempBuffer, tempSize)); + } + else + { + // #digits + 2 to avoid loss + 1 for the sign + 1 for decimal point + 5 for exponent (e+xxx) + 1 for null terminator + const size_t tempSize = std::numeric_limits::digits10 + 10; + wchar_t tempBuffer[tempSize]; + const int numChars = swprintf_s(tempBuffer, tempSize, L"%.*g", std::numeric_limits::digits10 + 2, m_number.m_value); + stream.append(tempBuffer, numChars); + } +} + +#endif + +odata::utility::string_t odata::utility::json::details::_String::as_string() const +{ + return m_string; +} + +odata::utility::string_t odata::utility::json::value::as_string() const +{ + return m_value->as_string(); +} + +odata::utility::string_t json::value::serialize() const { return m_value->to_string(); } \ No newline at end of file diff --git a/src/common/uri.cpp b/src/common/uri.cpp new file mode 100644 index 0000000..6e1ce2e --- /dev/null +++ b/src/common/uri.cpp @@ -0,0 +1,383 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" +#include "odata/common/uri.h" +#include "odata/common/uri_parser.h" +#include + +#define INTERNET_MAX_URL_LENGTH (2048) + +using namespace odata::utility::conversions; + +namespace odata { namespace utility { namespace details +{ +utility::string_t _uri_components::join() +{ + // canonicalize components first + + // convert scheme to lowercase + std::transform(m_scheme.begin(), m_scheme.end(), m_scheme.begin(), [this](utility::char_t c) { + return (utility::char_t)tolower(c); + }); + + // convert host to lowercase + std::transform(m_host.begin(), m_host.end(), m_host.begin(), [this](utility::char_t c) { + return (utility::char_t)tolower(c); + }); + + // canonicalize the path to have a leading slash if it's a full uri + if (!m_host.empty() && m_path.empty()) + { + m_path = _XPLATSTR("/"); + } + else if (!m_host.empty() && m_path[0] != _XPLATSTR('/')) + { + m_path.insert(m_path.begin(), 1, _XPLATSTR('/')); + } + + utility::ostringstream_t os; + + if (!m_scheme.empty()) + { + os << m_scheme << _XPLATSTR(':'); + } + + if (!m_host.empty()) + { + os << _XPLATSTR("//") << m_host; + + if (m_port > 0) + { + os << _XPLATSTR(':') << m_port; + } + } + + if (!m_path.empty()) + { + // only add the leading slash when the host is present + if (!m_host.empty() && m_path.front() != _XPLATSTR('/')) + { + os << _XPLATSTR('/'); + } + os << m_path; + } + + if (!m_query.empty()) + { + os << _XPLATSTR('?') << m_query; + } + + if (!m_fragment.empty()) + { + os << _XPLATSTR('#') << m_fragment; + } + + return os.str(); +} +} + +using namespace details; + +#pragma region constructor + +uri::uri(const utility::string_t &uri_string) +{ + if (!details::uri_parser().parse(uri_string, m_components)) + { + throw uri_exception("provided uri is invalid: " + utility::conversions::to_utf8string(uri_string)); + } + m_uri = m_components.join(); +} + +uri::uri(const utility::char_t *uri_string): m_uri(uri_string) +{ + if (!details::uri_parser().parse(uri_string, m_components)) + { + throw uri_exception("provided uri is invalid: " + utility::conversions::to_utf8string(uri_string)); + } + m_uri = m_components.join(); +} + +#pragma endregion + + +#pragma region encoding + +utility::string_t uri::encode_impl(const utility::string_t &raw, const std::function& should_encode) +{ + const utility::char_t * const hex = _XPLATSTR("0123456789ABCDEF"); + utility::string_t encoded; + std::string utf8raw = to_utf8string(raw); + for (auto iter = utf8raw.begin(); iter != utf8raw.end(); ++iter) + { + // for utf8 encoded string, char ASCII can be greater than 127. + int ch = static_cast(*iter); + // ch should be same under both utf8 and utf16. + if(should_encode(ch)) + { + encoded.push_back(_XPLATSTR('%')); + encoded.push_back(hex[(ch >> 4) & 0xF]); + encoded.push_back(hex[ch & 0xF]); + } + else + { + // ASCII don't need to be encoded, which should be same on both utf8 and utf16. + encoded.push_back((utility::char_t)ch); + } + } + return encoded; +} + +/// +/// Encodes a string by converting all characters except for RFC 3986 unreserved characters to their +/// hexadecimal representation. +/// +utility::string_t uri::encode_data_string(const utility::string_t &raw) +{ + return uri::encode_impl(raw, [](int ch) -> bool + { + return !uri_parser::is_unreserved(ch); + }); +} + +utility::string_t uri::encode_uri(const utility::string_t &raw, uri::components::component component) +{ + // Note: we also encode the '+' character because some non-standard implementations + // encode the space character as a '+' instead of %20. To better interoperate we encode + // '+' to avoid any confusion and be mistaken as a space. + switch(component) + { + case components::user_info: + return uri::encode_impl(raw, [](int ch) -> bool + { + return !uri_parser::is_user_info_character(ch) + || ch == '%' || ch == '+'; + }); + case components::host: + return uri::encode_impl(raw, [](int ch) -> bool + { + // No encoding of ASCII characters in host name (RFC 3986 3.2.2) + return ch > 127; + }); + case components::path: + return uri::encode_impl(raw, [](int ch) -> bool + { + return !uri_parser::is_path_character(ch) + || ch == '%' || ch == '+'; + }); + case components::query: + return uri::encode_impl(raw, [](int ch) -> bool + { + return !uri_parser::is_query_character(ch) + || ch == '%' || ch == '+'; + }); + case components::fragment: + return uri::encode_impl(raw, [](int ch) -> bool + { + return !uri_parser::is_fragment_character(ch) + || ch == '%' || ch == '+'; + }); + case components::full_uri: + default: + return uri::encode_impl(raw, [](int ch) -> bool + { + return !uri_parser::is_unreserved(ch) && !uri_parser::is_reserved(ch); + }); + }; +} + +/// +/// Helper function to convert a hex character digit to a decimal character value. +/// Throws an exception if not a valid hex digit. +/// +static int hex_char_digit_to_decimal_char(int hex) +{ + int decimal; + if(hex >= '0' && hex <= '9') + { + decimal = hex - '0'; + } + else if(hex >= 'A' && hex <= 'F') + { + decimal = 10 + (hex - 'A'); + } + else if(hex >= 'a' && hex <= 'f') + { + decimal = 10 + (hex - 'a'); + } + else + { + throw uri_exception("Invalid hexidecimal digit"); + } + return decimal; +} + +utility::string_t uri::decode(const utility::string_t &encoded) +{ + std::string utf8raw; + for(auto iter = encoded.begin(); iter != encoded.end(); ++iter) + { + if(*iter == _XPLATSTR('%')) + { + if(++iter == encoded.end()) + { + throw uri_exception("Invalid URI string, two hexidecimal digits must follow '%'"); + } + int decimal_value = hex_char_digit_to_decimal_char(static_cast(*iter)) << 4; + if(++iter == encoded.end()) + { + throw uri_exception("Invalid URI string, two hexidecimal digits must follow '%'"); + } + decimal_value += hex_char_digit_to_decimal_char(static_cast(*iter)); + + utf8raw.push_back(static_cast(decimal_value)); + } + else + { + // encoded string has to be ASCII. + utf8raw.push_back(reinterpret_cast(*iter)); + } + } + return to_string_t(utf8raw); +} + +#pragma endregion + +#pragma region splitting + +std::vector uri::split_path(const utility::string_t &path) +{ + std::vector results; + utility::istringstream_t iss(path); + utility::string_t s; + + while (std::getline(iss, s, _XPLATSTR('/'))) + { + if (!s.empty()) + { + results.push_back(s); + } + } + + return results; +} + +std::map uri::split_query(const utility::string_t &query) +{ + std::map results; + + // Split into key value pairs separated by '&'. + size_t prev_amp_index = 0; + while(prev_amp_index != utility::string_t::npos) + { + size_t amp_index = query.find_first_of(_XPLATSTR('&'), prev_amp_index); + if (amp_index == utility::string_t::npos) + amp_index = query.find_first_of(_XPLATSTR(';'), prev_amp_index); + + utility::string_t key_value_pair = query.substr( + prev_amp_index, + amp_index == utility::string_t::npos ? query.size() - prev_amp_index : amp_index - prev_amp_index); + prev_amp_index = amp_index == utility::string_t::npos ? utility::string_t::npos : amp_index + 1; + + size_t equals_index = key_value_pair.find_first_of(_XPLATSTR('=')); + if(equals_index == utility::string_t::npos) + { + continue; + } + else if (equals_index == 0) + { + utility::string_t value(key_value_pair.begin() + equals_index + 1, key_value_pair.end()); + results[_XPLATSTR("")] = value; + } + else + { + utility::string_t key(key_value_pair.begin(), key_value_pair.begin() + equals_index); + utility::string_t value(key_value_pair.begin() + equals_index + 1, key_value_pair.end()); + results[key] = value; + } + } + + return results; +} + +#pragma endregion + +#pragma region validation + +bool uri::validate(const utility::string_t &uri_string) +{ + return uri_parser().validate(uri_string); +} + +#pragma endregion + +#pragma region accessors +uri uri::authority() const +{ + return uri_builder().set_scheme(this->scheme()).set_host(this->host()).set_port(this->port()).set_user_info(this->user_info()).to_uri(); +} + +uri uri::resource() const +{ + return uri_builder().set_path(this->path()).set_query(this->query()).set_fragment(this->fragment()).to_uri(); +} +#pragma endregion + +#pragma region operators + +bool uri::operator == (const uri &other) const +{ + // Each individual URI component must be decoded before performing comparison. + // TFS # 375865 + + if (this->is_empty() && other.is_empty()) + { + return true; + } + else if (this->is_empty() || other.is_empty()) + { + return false; + } + else if (this->scheme() != other.scheme()) + { + // scheme is canonicalized to lowercase + return false; + } + else if(uri::decode(this->user_info()) != uri::decode(other.user_info())) + { + return false; + } + else if (uri::decode(this->host()) != uri::decode(other.host())) + { + // host is canonicalized to lowercase + return false; + } + else if (this->port() != other.port()) + { + return false; + } + else if (uri::decode(this->path()) != uri::decode(other.path())) + { + return false; + } + else if (uri::decode(this->query()) != uri::decode(other.query())) + { + return false; + } + else if (uri::decode(this->fragment()) != uri::decode(other.fragment())) + { + return false; + } + + return true; +} + +#pragma endregion + +}} diff --git a/src/common/uri_builder.cpp b/src/common/uri_builder.cpp new file mode 100644 index 0000000..b1182dc --- /dev/null +++ b/src/common/uri_builder.cpp @@ -0,0 +1,119 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" +#include "odata/common/uri.h" + +namespace odata { namespace utility +{ + +#pragma region Validation + +#pragma endregion + +#pragma region Appending + +uri_builder &uri_builder::append_path(const utility::string_t &path, bool is_encode) +{ + if(path.empty() || path == _XPLATSTR("/")) + { + return *this; + } + + auto encoded_path = is_encode ? uri::encode_uri(path, uri::components::path) : path; + auto thisPath = this->path(); + if(thisPath.empty() || thisPath == _XPLATSTR("/")) + { + if(encoded_path.front() != _XPLATSTR('/')) + { + set_path(_XPLATSTR("/") + encoded_path); + } + else + { + set_path(encoded_path); + } + } + else if(thisPath.back() == _XPLATSTR('/') && encoded_path.front() == _XPLATSTR('/')) + { + thisPath.pop_back(); + set_path(thisPath + encoded_path); + } + else if(thisPath.back() != _XPLATSTR('/') && encoded_path.front() != _XPLATSTR('/')) + { + set_path(thisPath + _XPLATSTR("/") + encoded_path); + } + else + { + // Only one slash. + set_path(thisPath + encoded_path); + } + return *this; +} + +uri_builder &uri_builder::append_query(const utility::string_t &query, bool is_encode) +{ + if(query.empty()) + { + return *this; + } + + auto encoded_query = is_encode ? uri::encode_uri(query, uri::components::path) : query; + auto thisQuery = this->query(); + if (thisQuery.empty()) + { + this->set_query(encoded_query); + } + else if(thisQuery.back() == _XPLATSTR('&') && encoded_query.front() == _XPLATSTR('&')) + { + thisQuery.pop_back(); + this->set_query(thisQuery + encoded_query); + } + else if(thisQuery.back() != _XPLATSTR('&') && encoded_query.front() != _XPLATSTR('&')) + { + this->set_query(thisQuery + _XPLATSTR("&") + encoded_query); + } + else + { + // Only one ampersand. + this->set_query(thisQuery + encoded_query); + } + return *this; +} + +uri_builder &uri_builder::append(const uri &relative_uri) +{ + append_path(relative_uri.path()); + append_query(relative_uri.query()); + this->set_fragment(this->fragment() + relative_uri.fragment()); + return *this; +} + +#pragma endregion + +#pragma region URI Creation + + +utility::string_t uri_builder::to_string() +{ + return to_uri().to_string(); +} + +uri uri_builder::to_uri() +{ + return uri(m_uri.join()); +} + +bool uri_builder::is_valid() +{ + return uri::validate(m_uri.join()); +} + +#pragma endregion + +} // namespace web +} diff --git a/src/common/uri_parser.cpp b/src/common/uri_parser.cpp new file mode 100644 index 0000000..e2c22d3 --- /dev/null +++ b/src/common/uri_parser.cpp @@ -0,0 +1,359 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/platform.h" +#include "odata/common/basic_types.h" +#include "odata/common/asyncrt_utils.h" +#include "odata/common/uri.h" +#include +#include "odata/common/uri_parser.h" +#include + +namespace odata { namespace utility { namespace details +{ + +const std::locale uri_parser::loc("C"); // use the C local to force the ASCII definitions of isalpha and friends + +bool uri_parser::validate(const utility::string_t &encoded_string) const +{ + const utility::char_t *scheme_begin = nullptr; + const utility::char_t *scheme_end = nullptr; + const utility::char_t *uinfo_begin = nullptr; + const utility::char_t *uinfo_end = nullptr; + const utility::char_t *host_begin = nullptr; + const utility::char_t *host_end = nullptr; + int port_ptr = 0; + const utility::char_t *path_begin = nullptr; + const utility::char_t *path_end = nullptr; + const utility::char_t *query_begin = nullptr; + const utility::char_t *query_end = nullptr; + const utility::char_t *fragment_begin = nullptr; + const utility::char_t *fragment_end = nullptr; + + // perform a parse, but don't copy out the data + return inner_parse( + encoded_string.c_str(), + &scheme_begin, + &scheme_end, + &uinfo_begin, + &uinfo_end, + &host_begin, + &host_end, + &port_ptr, + &path_begin, + &path_end, + &query_begin, + &query_end, + &fragment_begin, + &fragment_end); +} + +bool uri_parser::parse( + const utility::string_t &encoded_string, + _uri_components &components) const +{ + const utility::char_t *scheme_begin = nullptr; + const utility::char_t *scheme_end = nullptr; + const utility::char_t *host_begin = nullptr; + const utility::char_t *host_end = nullptr; + const utility::char_t *uinfo_begin = nullptr; + const utility::char_t *uinfo_end = nullptr; + int port_ptr = 0; + const utility::char_t *path_begin = nullptr; + const utility::char_t *path_end = nullptr; + const utility::char_t *query_begin = nullptr; + const utility::char_t *query_end = nullptr; + const utility::char_t *fragment_begin = nullptr; + const utility::char_t *fragment_end = nullptr; + + if (inner_parse( + encoded_string.c_str(), + &scheme_begin, + &scheme_end, + &uinfo_begin, + &uinfo_end, + &host_begin, + &host_end, + &port_ptr, + &path_begin, + &path_end, + &query_begin, + &query_end, + &fragment_begin, + &fragment_end)) + { + if (scheme_begin) + { + components.m_scheme.assign(scheme_begin, scheme_end); + + // convert scheme to lowercase + std::transform(components.m_scheme.begin(), components.m_scheme.end(), components.m_scheme.begin(), [this](utility::char_t c) { + return std::tolower(c, this->loc); + }); + } + else + { + components.m_scheme.clear(); + } + + if (uinfo_begin) + { + components.m_user_info.assign(uinfo_begin, uinfo_end); + } + + if (host_begin) + { + components.m_host.assign(host_begin, host_end); + + // convert host to lowercase + std::transform(components.m_host.begin(), components.m_host.end(), components.m_host.begin(), [this](utility::char_t c) { + return std::tolower(c, this->loc); + }); + } + else + { + components.m_host.clear(); + } + + if (port_ptr) + { + components.m_port = port_ptr; + } + else + { + components.m_port = 0; + } + + if (path_begin) + { + components.m_path.assign(path_begin, path_end); + } + else + { + // default path to begin with a slash for easy comparison + components.m_path = _XPLATSTR("/"); + } + + if (query_begin) + { + components.m_query.assign(query_begin, query_end); + } + else + { + components.m_query.clear(); + } + + if (fragment_begin) + { + components.m_fragment.assign(fragment_begin, fragment_end); + } + else + { + components.m_fragment.clear(); + } + + return true; + } + else + { + return false; + } + +} + +bool uri_parser::inner_parse( + const utility::char_t *encoded, + const utility::char_t **scheme_begin, const utility::char_t **scheme_end, + const utility::char_t **uinfo_begin, const utility::char_t **uinfo_end, + const utility::char_t **host_begin, const utility::char_t **host_end, + _Out_ int *port, + const utility::char_t **path_begin, const utility::char_t **path_end, + const utility::char_t **query_begin, const utility::char_t **query_end, + const utility::char_t **fragment_begin, const utility::char_t **fragment_end) const +{ + *scheme_begin = nullptr; + *scheme_end = nullptr; + *uinfo_begin = nullptr; + *uinfo_end = nullptr; + *host_begin = nullptr; + *host_end = nullptr; + *port = 0; + *path_begin = nullptr; + *path_end = nullptr; + *query_begin = nullptr; + *query_end = nullptr; + *fragment_begin = nullptr; + *fragment_end = nullptr; + + const utility::char_t *p = encoded; + + // IMPORTANT -- A uri may either be an absolute uri, or an relative-reference + // Absolute: 'http://host.com' + // Relative-Reference: '//:host.com', '/path1/path2?query', './path1:path2' + // A Relative-Reference can be disambiguated by parsing for a ':' before the first slash + + bool is_relative_reference = true; + const utility::char_t *p2 = p; + for (;*p2 != _XPLATSTR('/') && *p2 != _XPLATSTR('\0'); p2++) + { + if (*p2 == _XPLATSTR(':')) + { + // found a colon, the first portion is a scheme + is_relative_reference = false; + break; + } + } + + if (!is_relative_reference) + { + // the first character of a scheme must be a letter + if (!isalpha(*p)) + { + return false; + } + + // start parsing the scheme, it's always delimited by a colon (must be present) + *scheme_begin = p++; + for (;*p != ':'; p++) + { + if (!is_scheme_character(*p)) + { + return false; + } + } + *scheme_end = p; + + // skip over the colon + p++; + } + + // if we see two slashes next, then we're going to parse the authority portion + // later on we'll break up the authority into the port and host + const utility::char_t *authority_begin = nullptr; + const utility::char_t *authority_end = nullptr; + if (*p == _XPLATSTR('/') && p[1] == _XPLATSTR('/')) + { + // skip over the slashes + p += 2; + authority_begin = p; + + // the authority is delimited by a slash (resource), question-mark (query) or octothorpe (fragment) + // or by EOS. The authority could be empty ('file:///C:\file_name.txt') + for (;*p != _XPLATSTR('/') && *p != _XPLATSTR('?') && *p != _XPLATSTR('#') && *p != _XPLATSTR('\0'); p++) + { + // We're NOT currently supporting IPv6, IPvFuture or username/password in authority + if (!is_authority_character(*p)) + { + return false; + } + } + authority_end = p; + + // now lets see if we have a port specified -- by working back from the end + if (authority_begin != authority_end) + { + // the port is made up of all digits + const utility::char_t *port_begin = authority_end - 1; + for (;isdigit(*port_begin) && port_begin != authority_begin; port_begin--) + { } + + if (*port_begin == _XPLATSTR(':')) + { + // has a port + *host_begin = authority_begin; + *host_end = port_begin; + + //skip the colon + port_begin++; + + utility::istringstream_t port_str(utility::string_t(port_begin, authority_end)); + int port_num; + port_str >> port_num; + *port = port_num; + } + else + { + // no port + *host_begin = authority_begin; + *host_end = authority_end; + } + + // look for a user_info component + const utility::char_t *u_end = *host_begin; + for (;is_user_info_character(*u_end) && u_end != *host_end; u_end++) + { } + + if (*u_end == _XPLATSTR('@')) + { + *host_begin = u_end+1; + *uinfo_begin = authority_begin; + *uinfo_end = u_end; + } + else + { + uinfo_end = uinfo_begin = nullptr; + } + } + } + + // if we see a path character or a slash, then the + // if we see a slash, or any other legal path character, parse the path next + if (*p == _XPLATSTR('/') || is_path_character(*p)) + { + *path_begin = p; + + // the path is delimited by a question-mark (query) or octothorpe (fragment) or by EOS + for (;*p != _XPLATSTR('?') && *p != _XPLATSTR('#') && *p != _XPLATSTR('\0'); p++) + { + if (!is_path_character(*p)) + { + return false; + } + } + *path_end = p; + } + + // if we see a ?, then the query is next + if (*p == _XPLATSTR('?')) + { + // skip over the question mark + p++; + *query_begin = p; + + // the query is delimited by a '#' (fragment) or EOS + for (;*p != _XPLATSTR('#') && *p != _XPLATSTR('\0'); p++) + { + if (!is_query_character(*p)) + { + return false; + } + } + *query_end = p; + } + + // if we see a #, then the fragment is next + if (*p == _XPLATSTR('#')) + { + // skip over the hash mark + p++; + *fragment_begin = p; + + // the fragment is delimited by EOS + for (;*p != _XPLATSTR('\0'); p++) + { + if (!is_fragment_character(*p)) + { + return false; + } + } + *fragment_end = p; + } + + return true; +} + +}} +} diff --git a/src/common/utility.cpp b/src/common/utility.cpp new file mode 100644 index 0000000..a28312e --- /dev/null +++ b/src/common/utility.cpp @@ -0,0 +1,182 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/common/utility.h" + +namespace odata { namespace utility +{ + +string_t strip_string(const string_t& escaped) +{ + string_t::size_type first = 0; + string_t::size_type size = escaped.size(); + + if (escaped.empty()) + { + return escaped; + } + + if (escaped[0] == U('"')) + { + first += 1; + } + + if (escaped[size - 1] == U('"')) + { + size -= 1; + } + + return escaped.substr(first, size - first); +} + +void split_string(string_t& source, const string_t& delim, std::list& ret) +{ + ret.clear(); + + if (delim.empty() || source.empty()) + { + ret.push_back(source); + return ; + } + + size_t last = 0; + size_t index = source.find(delim, last); + + while (index!=std::string::npos) + { + ret.push_back(source.substr(last, index - last)); + last = index + delim.size(); + index = source.find(delim, last); + } + + if(index - last > 0) + { + ret.push_back(source.substr(last, index - last)); + } +} + +bool is_relative_path(const string_t& _root_url, const string_t& _path) +{ + if (_root_url.empty() || _path.empty()) + { + return false; + } + + string_t root_url = _root_url; + string_t path = _path; + + if (root_url.length() > path.length()) + { + return true; + } + + std::transform(root_url.begin(), root_url.end(), root_url.begin(), ::tolower); + std::transform(path.begin(), path.end(), path.begin(), ::tolower); + + size_t index = path.find(root_url); + + return path.find(root_url) != 0 ? true : false; +} + +string_t print_double(const double& db, int precision) +{ + ostringstream_t oss; + oss << std::setiosflags(std::ios::fixed) << std::setiosflags(std::ios::right) << std::setprecision(precision) << db; + if (oss.bad()) + { + throw std::bad_cast(); + } + + string_t output = oss.str(); + int dot = output.find(U('.')); + if (dot > 0) + { + int i = output.length() - 1; + for (i = output.length() - 1; i > dot; i--) + { + if (output[i] != U('0')) + { + break; + } + } + + if (i == dot) + { + i++; + } + + output = output.substr(0, i + 1); + } + + return output; +} + +string_t print_float(const float& db, int precision) +{ + ostringstream_t oss; + oss << std::setiosflags(std::ios::fixed) << std::setiosflags(std::ios::right) << std::setprecision(precision) << db; + if (oss.bad()) + { + throw std::bad_cast(); + } + + string_t output = oss.str(); + int dot = output.find(U('.')); + if (dot > 0) + { + int i = output.length() - 1; + for (i = output.length() - 1; i > dot; i--) + { + if (output[i] != U('0')) + { + break; + } + } + + if (i == dot) + { + i++; + } + + output = output.substr(0, i + 1); + } + + return output; +} + +bool is_digit(char_t c) +{ +#ifdef _UTF16_STRINGS + return iswdigit(c) != 0; +#else + return isdigit(c) != 0; +#endif +} + +bool is_hex_digit(char_t c) +{ +#ifdef _UTF16_STRINGS + return iswxdigit(c) != 0; +#else + return isxdigit(c) != 0; +#endif +} + +bool is_letter(char_t c) +{ +#ifdef _UTF16_STRINGS + return iswalpha(c) != 0; +#else + return isalpha(c) != 0; +#endif +} + +bool is_letter_or_digit(char_t c) +{ + return is_letter(c) || is_digit(c); +} + +}} \ No newline at end of file diff --git a/src/common/xmlhelpers.cpp b/src/common/xmlhelpers.cpp new file mode 100644 index 0000000..5a79777 --- /dev/null +++ b/src/common/xmlhelpers.cpp @@ -0,0 +1,544 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* 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. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* xmlhelpers.cpp +* +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ + +#include "odata/common/xmlhelpers.h" + +#ifdef WIN32 +#include "odata/common/xmlstream.h" +#else +#include "odata/common/asyncrt_utils.h" +typedef int XmlNodeType; +#define XmlNodeType_Element XML_READER_TYPE_ELEMENT +#define XmlNodeType_Text XML_READER_TYPE_TEXT +#define XmlNodeType_EndElement XML_READER_TYPE_END_ELEMENT +#endif + +using std::istream; +using std::string; +using namespace ::odata::utility; + +namespace odata { namespace edm { + + void xml_reader::initialize(istream& stream) + { + remove_bom(stream); + +#ifdef WIN32 + HRESULT hr; + CComPtr pInputStream; + pInputStream.Attach(xmlstring_istream::create(stream)); + + if (pInputStream == nullptr) + { + auto error = ERROR_NOT_ENOUGH_MEMORY; + string msg("XML reader IStream creation failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + if (FAILED(hr = CreateXmlReader(__uuidof(IXmlReader), (void**)&m_reader, NULL))) + { + auto error = GetLastError(); + string msg("XML reader CreateXmlReader failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + if (FAILED(hr = m_reader->SetProperty(XmlReaderProperty_DtdProcessing, DtdProcessing_Prohibit))) + { + auto error = GetLastError(); + string msg("XML reader SetProperty failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + if (FAILED(hr = m_reader->SetInput(pInputStream))) + { + auto error = GetLastError(); + string msg("XML reader SetInput failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + m_data = read_to_end(stream); + const char* xmlBuffer = m_data.data(); + unsigned int size = static_cast(m_data.size()); + xmlInitParser(); + m_reader = xmlReaderForMemory(xmlBuffer, size, "", "", XML_PARSE_RECOVER); +#endif + } + + /// + /// Remove Byte Order Mark from the stream + /// + void xml_reader::remove_bom(istream& stream) + { + // Synchronous. + if (stream.peek() == 0xEF + && stream.peek() == 0xBB + && stream.peek() == 0xBF) + { + stream.get(); + stream.get(); + stream.get(); + return; + } + } + + bool xml_reader::parse() + { + if (m_streamDone) return false; + // Set this to true each time the parse routine is invoked. Most derived readers will only invoke parse once. + m_continueParsing = true; + + // read until there are no more nodes +#ifdef WIN32 + HRESULT hr; + XmlNodeType nodeType; + while (m_continueParsing && S_OK == (hr = m_reader->Read(&nodeType))) + { +#else + if (m_reader == nullptr) + return !m_continueParsing; // no XML document to read + + while (m_continueParsing && xmlTextReaderRead(m_reader)) + { + auto nodeType = xmlTextReaderNodeType(m_reader); +#endif + switch (nodeType) + { + + case XmlNodeType_Element: + { + auto name = get_current_element_name(); +#ifdef WIN32 + BOOL is_empty_element = m_reader->IsEmptyElement(); +#else + bool is_empty_element = xmlTextReaderIsEmptyElement(m_reader); +#endif + m_elementStack.push_back(name); + handle_begin_element(name); + + if (is_empty_element) + { + handle_end_element(name); + m_elementStack.pop_back(); + } + } + break; + + case XmlNodeType_Text: + handle_element(m_elementStack.back()); + break; + + case XmlNodeType_EndElement: + handle_end_element(m_elementStack.back()); + m_elementStack.pop_back(); + break; + + default: + break; + } + } + + // If the loop was terminated because there was no more to read from the stream, set m_streamDone to true, so exit early + // the next time parse is invoked. + if (m_continueParsing) m_streamDone = true; + // Return false if the end of the stream was reached and true if parsing was paused. The return value indicates whether + // parsing can be resumed. + return !m_continueParsing; + } + + string_t xml_reader::get_parent_element_name(size_t pos) + { + if (m_elementStack.size() > pos + 1) + { + size_t currentDepth = m_elementStack.size() - 1; + size_t parentDepth = currentDepth - 1; + + if (pos <= parentDepth) + { + return m_elementStack[parentDepth - pos]; + } + } + + // return empty string + return string_t(); + } + + string_t xml_reader::get_current_element_name() + { +#ifdef WIN32 + HRESULT hr; + const wchar_t * pwszLocalName = NULL; + + if (FAILED(hr = m_reader->GetLocalName(&pwszLocalName, NULL))) + { + auto error = GetLastError(); + string msg("XML reader GetLocalName failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + return string_t(pwszLocalName); +#else + xmlChar* valueXml = xmlTextReaderLocalName(m_reader); + std::string input((char*)valueXml); + ::odata::utility::string_t stringt = ::odata::utility::conversions::to_string_t(input); + xmlFree(valueXml); + return stringt; +#endif + } + + string_t xml_reader::get_current_element_name_with_prefix() + { +#ifdef WIN32 + HRESULT hr; + const wchar_t * pwszName = NULL; + + if (FAILED(hr = m_reader->GetQualifiedName(&pwszName, NULL))) + { + auto error = GetLastError(); + string msg("XML reader GetQualified failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + return string_t(pwszName); +#else + throw std::runtime_error("Not implemented"); +#endif + } + + string_t xml_reader::get_current_element_text() + { +#ifdef WIN32 + HRESULT hr; + const wchar_t * pwszValue; + + if (FAILED(hr = m_reader->GetValue(&pwszValue, NULL))) + { + auto error = GetLastError(); + string msg("XML reader GetValue failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + return string_t(pwszValue); +#else + xmlChar* valueXml = xmlTextReaderValue(m_reader); + std::string input((char*)valueXml); + ::odata::utility::string_t stringt = ::odata::utility::conversions::to_string_t(input); + xmlFree(valueXml); + return stringt; +#endif + } + + bool xml_reader::move_to_first_attribute() + { +#ifdef WIN32 + HRESULT hr; + if (FAILED(hr = m_reader->MoveToFirstAttribute())) + { + auto error = GetLastError(); + string msg("XML reader MoveToFirstAttribute failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + return (hr == S_OK); +#else + return xmlTextReaderMoveToFirstAttribute(m_reader) > 0; +#endif + } + + bool xml_reader::move_to_next_attribute() + { +#ifdef WIN32 + HRESULT hr; + if (FAILED(hr = m_reader->MoveToNextAttribute())) + { + auto error = GetLastError(); + string msg("XML reader MoveToNextAttribute failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + return (hr == S_OK); +#else + return xmlTextReaderMoveToNextAttribute(m_reader) > 0; +#endif + } + + ::odata::utility::string_t xml_reader::read_to_end(std::istream& stream) + { +#ifdef WIN32 + throw std::runtime_error("xml_reader::read_to_end() should not be called in Windows"); +#else // LINUX + ::odata::utility::string_t text; + while (stream.good()) + { + int c = stream.get(); + if (c == -1) + { + break; + } + text.push_back(c); + } + return text; +#endif + } + + void xml_writer::initialize(std::ostream& stream) + { +#ifdef WIN32 + HRESULT hr; + CComPtr pStream; + pStream.Attach(xmlstring_ostream::create(stream)); + + if (pStream == nullptr) + { + auto error = ERROR_NOT_ENOUGH_MEMORY; + string msg("XML writer IStream creation failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + if (FAILED(hr = CreateXmlWriter(__uuidof(IXmlWriter), (void**)&m_writer, NULL))) + { + auto error = GetLastError(); + string msg("XML writer CreateXmlWriter failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + if (FAILED(hr = m_writer->SetOutput(pStream))) + { + auto error = GetLastError(); + string msg("XML writer SetOutput failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + if (FAILED(hr = m_writer->SetProperty(XmlWriterProperty_Indent, TRUE))) + { + auto error = GetLastError(); + string msg("XML writer SetProperty failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + + if (FAILED(hr = m_writer->WriteStartDocument(XmlStandalone_Omit))) + { + auto error = GetLastError(); + string msg("XML writer WriteStartDocument failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else // LINUX + m_writer = xmlNewTextWriterDoc(&m_doc, 0); + m_stream = &stream; +#endif + } + + void xml_writer::finalize() + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteEndDocument())) + { + auto error = GetLastError(); + string msg("XML writer WriteEndDocument failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } + if (FAILED(hr = m_writer->Flush())) + { + auto error = GetLastError(); + string msg("XML writer Flush failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else // LINUX + xmlChar *memory; + int size; + xmlDocDumpMemory(m_doc, &memory, &size); + *m_stream << memory; + xmlFree(memory); + +#endif + } + + void xml_writer::write_start_element_with_prefix(const string_t& elementPrefix, const string_t& elementName, const string_t& namespaceName) + { +#ifdef WIN32 + HRESULT hr; + if (FAILED(hr = m_writer->WriteStartElement(elementPrefix.c_str(), elementName.c_str(), namespaceName.empty() ? NULL : namespaceName.c_str()))) + { + auto error = GetLastError(); + string msg("XML writer WriteStartElement with prefix failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + xmlChar* valueXml = (xmlChar*) ::odata::utility::conversions::to_utf8string(elementName).c_str(); + xmlTextWriterStartElement(m_writer, valueXml); + xmlFree(valueXml); +#endif + } + + void xml_writer::write_start_element(const string_t& elementName, const string_t& namespaceName) + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteStartElement(NULL, elementName.c_str(), namespaceName.empty() ? NULL : namespaceName.c_str()))) + { + auto error = GetLastError(); + string msg("XML writer WriteStartElement failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + write_start_element_with_prefix(U(""), elementName, namespaceName); +#endif + } + + void xml_writer::write_end_element() + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteEndElement())) + { + auto error = GetLastError(); + string msg("XML writer WriteEndElement failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + xmlTextWriterEndElement(m_writer); +#endif + } + + void xml_writer::write_full_end_element() + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteFullEndElement())) + { + auto error = GetLastError(); + string msg("XML writer WriteFullEndElement failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + throw std::runtime_error("Not implemented"); +#endif + } + + void xml_writer::write_string(const string_t& str) + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteString(str.c_str()))) + { + auto error = GetLastError(); + string msg("XML writer WriteString failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + UNREFERENCED_PARAMETER(str); + throw std::runtime_error("Not implemented"); +#endif + } + + + void xml_writer::write_attribute_string(const string_t& prefix, const string_t& name, const string_t& namespaceUri, const string_t& value) + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteAttributeString(prefix.empty() ? NULL : prefix.c_str(), + name.empty() ? NULL : name.c_str(), + namespaceUri.empty() ? NULL : namespaceUri.c_str(), + value.empty() ? NULL : value.c_str()))) + { + auto error = GetLastError(); + string msg("XML writer WriteAttributeString failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + xmlChar* nameXml = (xmlChar*) ::odata::utility::conversions::to_utf8string(name).c_str(); + xmlChar* valueXml = (xmlChar*) ::odata::utility::conversions::to_utf8string(value).c_str(); + xmlTextWriterWriteAttribute(m_writer, nameXml, valueXml); + xmlFree(nameXml); + xmlFree(valueXml); +#endif + } + + void xml_writer::write_element(const string_t& elementName, const string_t& value) + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteElementString(NULL, elementName.c_str(), NULL, value.c_str()))) + { + auto error = GetLastError(); + string msg("XML writer WriteElementString failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else // LINUX + write_element_with_prefix(U(""), elementName, value); +#endif + } + + void xml_writer::write_element_with_prefix(const string_t& prefix, const string_t& elementName, const string_t& value) + { +#ifdef WIN32 + HRESULT hr; + + if (FAILED(hr = m_writer->WriteElementString(prefix.c_str(), elementName.c_str(), NULL, value.c_str()))) + { + auto error = GetLastError(); + string msg("XML writer WriteElementStringWithPrefix failed"); + log_error_message(msg, error); + throw std::runtime_error(msg); + } +#else + write_start_element_with_prefix(prefix, elementName); + write_string(value); + write_end_element(); +#endif + } +}} +// namespace odata::edm diff --git a/src/core/odata_context_url_builder.cpp b/src/core/odata_context_url_builder.cpp new file mode 100644 index 0000000..1910a39 --- /dev/null +++ b/src/core/odata_context_url_builder.cpp @@ -0,0 +1,14 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_context_url_builder.h" + +using namespace odata::utility::json; +using namespace odata::edm; + +namespace odata { namespace core +{ +}} \ No newline at end of file diff --git a/src/core/odata_context_url_parser.cpp b/src/core/odata_context_url_parser.cpp new file mode 100644 index 0000000..2584806 --- /dev/null +++ b/src/core/odata_context_url_parser.cpp @@ -0,0 +1,215 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_context_url_parser.h" +#include "odata/edm/edm_model_utility.h" +#include "odata/core/odata_uri_parser.h" + +using namespace ::odata::edm; + +namespace odata { namespace core +{ + +bool odata_contex_url_parser::end_with(const ::odata::utility::string_t& s1, const ::odata::utility::string_t& s2) +{ + auto found = s1.rfind(s2); + if (found != ::odata::utility::string_t::npos) + { + return found == s1.size() - s2.size(); + } + + return false; +} + +std::shared_ptr odata_contex_url_parser::resolve_literal_type(const ::odata::utility::string_t& type_name) +{ + if (type_name.substr(0, 4) == U("Edm.")) + { + return edm_model_utility::get_edm_primitive_type_from_name(type_name); + } + else if (type_name.find(U("Collection")) == 0) + { + auto inside_name = type_name.substr(11, type_name.length() - 12); + return std::make_shared(resolve_literal_type(inside_name)); + } + else + { + std::shared_ptr ret_type; + + ret_type = m_model->find_complex_type(type_name); + if (ret_type) + { + return ret_type; + } + + ret_type = m_model->find_enum_type(type_name); + if (ret_type) + { + return ret_type; + } + + ret_type = m_model->find_entity_type(type_name); + if (ret_type) + { + return ret_type; + } + + throw std::runtime_error("Failed to parse context url"); + } +} + +std::shared_ptr odata_contex_url_parser::get_payload_content_type(const ::odata::utility::string_t& context_url) +{ + if (!m_model || m_service_root_url.empty()) + { + return nullptr; + } + + ::odata::utility::string_t path = context_url; + ::odata::utility::string_t root = m_service_root_url + U("/$metadata#"); + + path = context_url.substr(context_url.find(U("$metadata#")) + 10); + bool select_single_entity = end_with(path, U("/$entity")); + if (select_single_entity) + { + path = path.substr(0, path.size()-8); + } + + //Handle select/expand items. Right now just remove them. + int index_last_slash = path.rfind(U("/")); + int index_last_bracket = path.find_first_of(U("("), ++index_last_slash); + if (index_last_slash < index_last_bracket) + { + auto first_part = path.substr(0, index_last_bracket); + if (first_part != U("Collection")) + { + path = first_part; + } + } + + if (path.find(U("/")) == ::odata::utility::string_t::npos && !select_single_entity) + { + //1.service document; 2.entity set or singleton; 3.primitive/complex type; 4. collection of primitive/complex type + std::shared_ptr container = m_model->find_container(); + if (container) + { + auto entity_set = container->find_entity_set(path); + if (entity_set) + { + return std::make_shared(entity_set->get_entity_type()); + } + auto singleton = container->find_singleton(path); + if (singleton) + { + return singleton->get_entity_type(); + } + } + + return resolve_literal_type(path); + } + else + { + auto path_parser = std::make_shared(m_model); + auto odata_path = path_parser->parse_path(U("/") + path); + + auto& segments = odata_path->segments(); + if (segments.size() == 0) + { + throw std::runtime_error("Invalid context url"); + } + + std::shared_ptr last_segment; + int index; + for (index = segments.size() - 1; index >=0; index--) + { + if (segments[index]->segment_type() != odata_path_segment_type::Type) + { + last_segment = segments[index]; + break; + } + } + + std::shared_ptr ret_type; + if (last_segment->segment_type() == odata_path_segment_type::EntitySet) + { + if (select_single_entity) + { + ret_type = last_segment->as()->entity_type(); + } + else + { + auto entity_type = last_segment->as()->entity_type(); + ret_type = std::make_shared(entity_type); + } + } + else if (last_segment->segment_type() == odata_path_segment_type::Singleton) + { + ret_type = last_segment->as()->entity_type(); + } + else if (last_segment->segment_type() == odata_path_segment_type::NavigationProperty) + { + auto edm_navigation_type = last_segment->as()->navigation_type(); + if (!edm_navigation_type->is_contained()) + { + throw std::runtime_error("Invalid context url"); + } + auto navigation_type = edm_navigation_type->get_navigation_type(); + if (navigation_type->get_type_kind() == edm_type_kind_t::Collection && select_single_entity) + { + auto collection_entity_type = std::dynamic_pointer_cast(navigation_type); + ret_type = collection_entity_type->get_element_type(); + } + else + { + ret_type = navigation_type; + } + } + else if (last_segment->segment_type() == odata_path_segment_type::StructuralProperty) + { + auto edm_property_type = last_segment->as()->property(); + auto property_type = edm_property_type->get_property_type(); + + ret_type = property_type; + } + else + { + throw std::runtime_error("Invalid context url"); + } + + if (index != segments.size() - 1) + { + last_segment = segments[segments.size() - 1]; + if (last_segment->segment_type() == odata_path_segment_type::Type) + { + auto target_type = last_segment->as()->type(); + if (ret_type->get_type_kind() == edm_type_kind_t::Collection) + { + std::dynamic_pointer_cast(ret_type)->set_element_type(target_type); + } + else + { + ret_type = target_type; + } + } + } + + return ret_type; + } +} + +std::shared_ptr odata_contex_url_parser::parse_complex_or_primitive(const ::odata::utility::string_t& current_path) +{ + std::shared_ptr current_type = edm_model_utility::get_edm_primitive_type_from_name(current_path); + + if (!current_type) + { + current_type = m_model->find_complex_type(current_path); + } + + return current_type; +} + +}} \ No newline at end of file diff --git a/src/core/odata_entity_model_builder.cpp b/src/core/odata_entity_model_builder.cpp new file mode 100644 index 0000000..f66ed0f --- /dev/null +++ b/src/core/odata_entity_model_builder.cpp @@ -0,0 +1,95 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_entity_model_builder.h" +#include "odata/common/utility.h" + +using namespace ::odata::edm; + +namespace odata { namespace core +{ + +::odata::utility::string_t odata_entity_model_builder::compute_edit_link( + const ::odata::utility::string_t& root_url, + std::shared_ptr entity_value, + const ::odata::utility::string_t& parent_edit_link, + bool is_collection_navigation) +{ + ::odata::utility::string_t edit_link; + + if (entity_value) + { + if (!entity_value->get_edit_link().is_empty()) + { + edit_link = entity_value->get_edit_link().to_string(); + + if (::odata::utility::is_relative_path(root_url, edit_link)) + { + edit_link = root_url + U("/") + edit_link; + } + } + else + { + if (is_collection_navigation) + { + ::odata::utility::string_t key_string = get_entity_key_value_string(entity_value); + edit_link = parent_edit_link + U("(") + key_string + U(")"); + } + else + { + edit_link = parent_edit_link; + } + } + } + + return edit_link; +} + +::odata::utility::string_t odata_entity_model_builder::get_entity_key_value_string(const std::shared_ptr& entity_value) +{ + ::odata::utility::string_t key_string; + + if (!entity_value) + { + return key_string; + } + + std::vector<::odata::utility::string_t> keys; + + auto parent_type = std::dynamic_pointer_cast(entity_value->get_value_type()); + while (parent_type) + { + keys.insert(keys.end(), parent_type->key().cbegin(), parent_type->key().cend()); + parent_type = std::dynamic_pointer_cast(parent_type->get_base_type()); + } + + for (size_t i = 0; i < keys.size(); i++) + { + std::shared_ptr property_value; + entity_value->get_property_value(keys[i], property_value); + if (property_value) + { + std::shared_ptr property_type = property_value->get_value_type(); + if (property_type && property_type->get_type_kind() == edm_type_kind_t::Primitive) + { + auto primitive_property_value = std::dynamic_pointer_cast(property_value); + if (primitive_property_value) + { + if (i != 0) + { + key_string += U(","); + } + + key_string += primitive_property_value->to_string(); + } + } + } + } + + return key_string; +} + +}} \ No newline at end of file diff --git a/src/core/odata_entity_value.cpp b/src/core/odata_entity_value.cpp new file mode 100644 index 0000000..51b0f99 --- /dev/null +++ b/src/core/odata_entity_value.cpp @@ -0,0 +1,67 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_entity_value.h" +#include "odata/edm/edm_model_utility.h" + +using namespace ::odata::edm; +using namespace ::odata::utility; + +namespace odata { namespace core +{ + +::odata::utility::string_t odata_entity_value::get_entity_key_string() +{ + ::odata::utility::string_t key; + + auto entitytype = std::dynamic_pointer_cast(get_value_type()); + + if (entitytype) + { + key += U("("); + + std::vector<::odata::utility::string_t> key_property_names = entitytype->get_key_with_parents(); + for (size_t i = 0; i < key_property_names.size(); i++) + { + std::shared_ptr property_value; + get_property_value(key_property_names[i], property_value); + if (property_value) + { + auto property_type = property_value->get_value_type(); + if (property_type && property_type->get_type_kind() == edm_type_kind_t::Primitive) + { + if (i != 0) + { + key += U(","); + } + + auto primitive_property_value = std::dynamic_pointer_cast(property_value); + if (primitive_property_value) + { + if (key_property_names.size() == 1) + { + key += primitive_property_value->to_string(); + } + else + { + key += key_property_names[i] + U("=") + primitive_property_value->to_string(); + } + } + } + else + { + throw std::runtime_error("entity key type error!"); + } + } + } + + key += U(")"); + } + + return key; +} + +}} \ No newline at end of file diff --git a/src/core/odata_json_operation_payload_parameter_writer.cpp b/src/core/odata_json_operation_payload_parameter_writer.cpp new file mode 100644 index 0000000..6f2572e --- /dev/null +++ b/src/core/odata_json_operation_payload_parameter_writer.cpp @@ -0,0 +1,334 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_json_operation_payload_parameter_writer.h" + +using namespace ::odata::edm; + +namespace odata { namespace core +{ + +::odata::utility::json::value odata_json_operation_payload_parameter_writer::serialize(std::vector> parameters) +{ + if (parameters.empty()) + { + return odata::utility::json::value(); + } + + ::odata::utility::stringstream_t ss; + ss << U("{"); + + auto iter = parameters.cbegin(); + handle_serialize_odata_parameter(ss, *iter, U('"'), U(':')); + iter++; + for(; iter != parameters.cend(); iter++) + { + ss << U(","); + handle_serialize_odata_parameter(ss, *iter, U('"'), U(':')); + } + + ss << U("}"); + + return odata::utility::json::value::parse(ss); +} + +void odata_json_operation_payload_parameter_writer::handle_serialize_odata_parameter( + ::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::core::odata_parameter>& parameter, ::odata::utility::char_t mark, ::odata::utility::char_t separator) +{ + if (parameter && parameter->get_value() && !parameter->get_name().empty()) + { + if (mark) + { + ss << mark << parameter->get_name() << mark; + } + else + { + ss << parameter->get_name(); + } + + ss << separator; + + handle_serialize_odata_value(ss, parameter->get_value()->get_value_type(), parameter->get_value()); + } +} + +void odata_json_operation_payload_parameter_writer::handle_serialize_odata_properties(::odata::utility::stringstream_t& ss, const odata_property_map& properties) +{ + if (properties.size() <= 0) + { + ss << "null"; + + return ; + } + + ss << U("{"); + + bool first = true; + + for (auto iter = properties.cbegin(); iter != properties.cend(); iter++) + { + if (!iter->second) + { + if (!first) + { + ss << U(","); + } + + ss << U('"') << iter->first << U('"') << U(":") << U("null"); + + first = false; + } + else + { + auto property_type = iter->second->get_value_type(); + + if (!is_type_serializable(property_type)) + { + continue; + } + + if (!first) + { + ss << U(","); + } + + ss << U('"') << iter->first << U('"') << U(":"); + + handle_serialize_odata_value(ss, property_type, iter->second); + + first = false; + } + + } + + ss << U("}"); +} + +void odata_json_operation_payload_parameter_writer::handle_serialize_odata_value( + ::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_named_type>& property_type, const std::shared_ptr& property_value) +{ + if (!property_type || !property_value) + { + ss << "null"; + + return ; + } + + switch(property_type->get_type_kind()) + { + case edm_type_kind_t::Primitive: + { + auto p_value = std::dynamic_pointer_cast(property_value); + auto p_primitive_type = std::dynamic_pointer_cast(property_type); + + if (p_value && p_primitive_type) + { + handle_serialize_primitive_value(ss, p_primitive_type, p_value); + } + else + { + ss << "null"; + } + } + break; + case edm_type_kind_t::PayloadAnnotation: + { + auto p_value = std::dynamic_pointer_cast(property_value); + + if (p_value) + { + ss << U('"') << p_value->to_string() << U('"'); + } + else + { + ss << "null"; + } + } + break; + case edm_type_kind_t::Enum: + { + auto p_value = std::dynamic_pointer_cast(property_value); + + if (p_value) + { + handle_serialize_enum_value(ss, p_value); + } + else + { + ss << "null"; + } + } + break; + case edm_type_kind_t::Complex: + { + auto p_value = std::dynamic_pointer_cast(property_value); + + if (p_value) + { + handle_serialize_odata_properties(ss, p_value->properties()); + } + else + { + ss << "null"; + } + } + break; + case edm_type_kind_t::Collection: + { + auto p_value = std::dynamic_pointer_cast(property_value); + + if (p_value) + { + handle_serialize_collection_value(ss, p_value); + } + else + { + ss << "[]"; + } + } + break; + case edm_type_kind_t::Entity: + { + throw std::runtime_error("writer unsupported type!"); + } + break; + default: + { + throw std::runtime_error("writer unsupported type!"); + } + break; + } +} + +void odata_json_operation_payload_parameter_writer::handle_serialize_primitive_value( + ::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_primitive_type>& p_primitive_type, const std::shared_ptr& p_value) +{ + if (!p_primitive_type || !p_value) + { + return ; + } + + switch(p_primitive_type->get_primitive_kind()) + { + case edm_primitive_type_kind_t::Boolean: + { + if (p_value->as()) + { + ss << U("true"); + } + else + { + ss << U("false"); + } + } + break; + case edm_primitive_type_kind_t::Byte: + case edm_primitive_type_kind_t::Decimal: + case edm_primitive_type_kind_t::Double: + case edm_primitive_type_kind_t::Int16: + case edm_primitive_type_kind_t::Int32: + case edm_primitive_type_kind_t::Int64: + case edm_primitive_type_kind_t::Single: + case edm_primitive_type_kind_t::SByte: + { + ss << p_value->to_string(); + } + break; + case edm_primitive_type_kind_t::Guid: + case edm_primitive_type_kind_t::Binary: + case edm_primitive_type_kind_t::DateTimeOffset: + case edm_primitive_type_kind_t::Duration: + { + ss << U('"') << p_value->to_string() << U('"'); + } + break; + case edm_primitive_type_kind_t::String: + { + ss << U('"') << p_value->to_string() << U('"'); + } + break; + case edm_primitive_type_kind_t::Stream: + { + throw std::runtime_error("stream primitive value not implemented!"); + } + break; + default: + { + throw std::runtime_error("unknown value not implemented!"); + } + break; + } +} + +void odata_json_operation_payload_parameter_writer::handle_serialize_enum_value(::odata::utility::stringstream_t& ss, const std::shared_ptr& p_value) +{ + if (!p_value) + { + return ; + } + + ss << U('"') << p_value->to_string() << U('"'); +} + +bool odata_json_operation_payload_parameter_writer::is_type_serializable(const std::shared_ptr& property_type) +{ + if (property_type) + { + if (property_type->get_type_kind() == edm_type_kind_t::Collection + || property_type->get_type_kind() == edm_type_kind_t::Entity + || property_type->get_type_kind() == edm_type_kind_t::Complex + || property_type->get_type_kind() == edm_type_kind_t::Primitive + || property_type->get_type_kind() == edm_type_kind_t::Enum + || (property_type->get_type_kind() == edm_type_kind_t::PayloadAnnotation && property_type->get_name() == PAYLOAD_ANNOTATION_TYPE)) + { + return true; + } + } + + return false; +} + +void odata_json_operation_payload_parameter_writer::handle_serialize_collection_value(::odata::utility::stringstream_t& ss, const std::shared_ptr& p_value) +{ + if (!p_value) + { + return ; + } + + if (p_value->get_collection_values().size() == 0) + { + ss << U("[]"); + + return ; + } + + ss << U("["); + + bool first = true; + + auto element_type = p_value->get_collection_values()[0]->get_value_type(); + + for (auto iter = p_value->get_collection_values().cbegin(); iter != p_value->get_collection_values().cend(); iter++) + { + if (!is_type_serializable(element_type)) + { + continue; + } + + if (!first) + { + ss << U(","); + } + + handle_serialize_odata_value(ss, element_type, *iter); + + first = false; + } + + ss << U("]"); +} + +}} \ No newline at end of file diff --git a/src/core/odata_json_operation_url_parameter_writer.cpp b/src/core/odata_json_operation_url_parameter_writer.cpp new file mode 100644 index 0000000..04a67e6 --- /dev/null +++ b/src/core/odata_json_operation_url_parameter_writer.cpp @@ -0,0 +1,194 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_json_operation_url_parameter_writer.h" + +using namespace ::odata::edm; + +namespace odata { namespace core +{ + +::odata::utility::string_t odata_json_operation_url_parameter_writer::serialize(std::vector> parameters) +{ + if (parameters.empty()) + { + return U("()"); + } + + ::odata::utility::stringstream_t ss; + ss << U("("); + + auto iter = parameters.cbegin(); + handle_serialize_odata_parameter(ss, *iter, U('\0'), U('=')); + iter++; + for(; iter != parameters.cend(); iter++) + { + ss << ","; + handle_serialize_odata_parameter(ss, *iter, U('\0'), U('=')); + } + + ss << U(")"); + + return ss.str(); +} + +void odata_json_operation_url_parameter_writer::handle_serialize_odata_parameter( + ::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::core::odata_parameter>& parameter, ::odata::utility::char_t mark, ::odata::utility::char_t separator) +{ + if (parameter && parameter->get_value() && !parameter->get_name().empty()) + { + if (mark) + { + ss << mark << parameter->get_name() << mark; + } + else + { + ss << parameter->get_name(); + } + + ss << separator; + + handle_serialize_odata_value(ss, parameter->get_value()->get_value_type(), parameter->get_value()); + } +} + +void odata_json_operation_url_parameter_writer::handle_serialize_odata_value( + ::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_named_type>& property_type, const std::shared_ptr& property_value) +{ + auto p_value = std::dynamic_pointer_cast(property_value); + if (p_value) + { + return handle_serialize_enum_value(ss, p_value); + } + + if (!property_type || !property_value) + { + ss << "null"; + + return ; + } + + switch(property_type->get_type_kind()) + { + case edm_type_kind_t::Primitive: + { + auto p_value = std::dynamic_pointer_cast(property_value); + auto p_primitive_type = std::dynamic_pointer_cast(property_type); + + if (p_value && p_primitive_type) + { + handle_serialize_primitive_value(ss, p_primitive_type, p_value); + } + else + { + ss << "null"; + } + } + break; + case edm_type_kind_t::Enum: + { + auto p_value = std::dynamic_pointer_cast(property_value); + + if (p_value) + { + handle_serialize_enum_value(ss, p_value); + } + else + { + ss << "null"; + } + } + break; + case edm_type_kind_t::Complex: + { + throw std::runtime_error("writer unsupported type!"); + } + break; + case edm_type_kind_t::Collection: + { + throw std::runtime_error("writer unsupported type!"); + } + break; + case edm_type_kind_t::Entity: + { + throw std::runtime_error("writer unsupported type!"); + } + break; + default: + { + throw std::runtime_error("writer unsupported type!"); + } + break; + } +} + +void odata_json_operation_url_parameter_writer::handle_serialize_primitive_value( + ::odata::utility::stringstream_t& ss, const std::shared_ptr<::odata::edm::edm_primitive_type>& p_primitive_type, const std::shared_ptr& p_value) +{ + if (!p_primitive_type || !p_value) + { + return ; + } + + switch(p_primitive_type->get_primitive_kind()) + { + case edm_primitive_type_kind_t::Boolean: + { + if (p_value->as()) + { + ss << U("true"); + } + else + { + ss << U("false"); + } + } + break; + case edm_primitive_type_kind_t::Byte: + case edm_primitive_type_kind_t::Decimal: + case edm_primitive_type_kind_t::Double: + case edm_primitive_type_kind_t::Int16: + case edm_primitive_type_kind_t::Int32: + case edm_primitive_type_kind_t::Int64: + case edm_primitive_type_kind_t::Single: + case edm_primitive_type_kind_t::SByte: + { + ss << p_value->to_string(); + } + break; + case edm_primitive_type_kind_t::Guid: + case edm_primitive_type_kind_t::String: + case edm_primitive_type_kind_t::Binary: + case edm_primitive_type_kind_t::DateTimeOffset: + case edm_primitive_type_kind_t::Duration: + { + ss << U('\'') << p_value->to_string() << U('\''); + } + break; + case edm_primitive_type_kind_t::Stream: + { + throw std::runtime_error("stream primitive value not implemented!"); + } + break; + default: + { + throw std::runtime_error("unknown value not implemented!"); + } + break; + } +} + +void odata_json_operation_url_parameter_writer::handle_serialize_enum_value(::odata::utility::stringstream_t& ss, const std::shared_ptr& p_value) +{ + if (!p_value) + { + return ; + } + + ss << p_value->to_string(); +} + +}} \ No newline at end of file diff --git a/src/core/odata_json_reader_full.cpp b/src/core/odata_json_reader_full.cpp new file mode 100644 index 0000000..aca9bab --- /dev/null +++ b/src/core/odata_json_reader_full.cpp @@ -0,0 +1,20 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_json_reader_full.h" + +using namespace ::odata::edm; +using namespace ::odata::utility; + +namespace odata { namespace core +{ + +std::shared_ptr entity_json_reader_full::deserilize(const odata::utility::json::value& response, std::shared_ptr set) +{ + throw std::runtime_error("full metadata reader not implemented!"); +} + +}} \ No newline at end of file diff --git a/src/core/odata_json_reader_minimal.cpp b/src/core/odata_json_reader_minimal.cpp new file mode 100644 index 0000000..eb88689 --- /dev/null +++ b/src/core/odata_json_reader_minimal.cpp @@ -0,0 +1,872 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_context_url_parser.h" +#include "odata/core/odata_entity_factory.h" +#include "odata/core/odata_json_reader_minimal.h" +#include "odata/core/odata_uri_parser.h" + +using namespace ::odata::edm; +using namespace ::odata::utility; + +namespace odata { namespace core +{ + +enum RET_NAVI_ENTITY_TYPE +{ + NAVI_ENTITY_NONE = 0, + NAVI_ENTITY_SINGLE, + NAVI_ENTITY_COLLECTION, + ENTITY_SINGLETON, + ENTITY_ENTITY_SET, +}; + +::odata::utility::string_t odata_json_reader_minimal::get_navigation_source_from_context_url(const ::odata::utility::string_t& context_url) +{ + ::odata::utility::string_t ret = context_url; + + int index = ret.find(U("#")); + ret = ret.substr(index + 1, ret.length() - index - 1); + + index = ret.find(U("/$entity")); + if (index != -1) + { + ret = ret.substr(0, index); + } + + int index_last_slash = ret.rfind(U("/")); + + int index_last_bracket = ret.find_first_of(U("("), ++index_last_slash); + if (index_last_slash < index_last_bracket) + { + ret = ret.substr(0, index_last_bracket); + } + + return ret; +} + +void odata_json_reader_minimal::set_edit_link_for_entity_collection_value(const std::shared_ptr& entity_collection_value, const ::odata::utility::string_t& expect_type_name, + const ::odata::utility::string_t& navigation_source) +{ + if (!entity_collection_value) + { + return ; + } + + auto collection_type = std::dynamic_pointer_cast(entity_collection_value->get_value_type()); + if (!collection_type || collection_type->get_element_type()->get_type_kind() != edm_type_kind_t::Entity) + { + return; + } + + for (auto iter = entity_collection_value->get_collection_values().cbegin(); iter != entity_collection_value->get_collection_values().cend(); iter++) + { + auto entity_value = std::dynamic_pointer_cast(*iter); + if (!entity_value || !entity_value->get_edit_link().is_empty()) + { + continue; + } + + auto key_string = entity_value->get_entity_key_string(); + entity_value->set_edit_link(navigation_source + key_string); + + // check to see if it is an derived type + if (expect_type_name != entity_value->get_value_type()->get_name()) + { + ::odata::utility::string_t derived_edit_link = navigation_source + key_string; + derived_edit_link += U("/"); + if (entity_value->has_property(PAYLOAD_ANNOTATION_TYPE)) + { + ::odata::utility::string_t derived_type; + entity_value->try_get(PAYLOAD_ANNOTATION_TYPE, derived_type); + if (!derived_type.empty() && derived_type[0] == U('#')) + derived_type = derived_type.substr(1, derived_type.length() - 1); + derived_edit_link += derived_type; + entity_value->set_edit_link(derived_edit_link); + } + } + } +} + + +void odata_json_reader_minimal::set_edit_link_for_entity_value(const std::shared_ptr& entity_value, const ::odata::utility::string_t& expect_type_name, + const ::odata::utility::string_t& navigation_source) +{ + if (!entity_value || !entity_value->get_edit_link().is_empty()) + { + return ; + } + + auto path_parser = std::make_shared(m_model); + ::odata::utility::string_t relative_path = navigation_source; + if (relative_path.find(m_service_root_url) == 0) + { + relative_path = relative_path.substr(m_service_root_url.size()); + } + if (relative_path.find(U("/")) != 0) + { + relative_path = U("/") + relative_path; + } + auto odata_path = path_parser->parse_path(relative_path); + + auto& segments = odata_path->segments(); + if (segments.size() == 0) + { + throw std::runtime_error("Invalid context url"); + } + std::shared_ptr last_segment; + for (int i = segments.size() - 1; i >=0; i--) + { + if (segments[i]->segment_type() != odata_path_segment_type::Type) + { + last_segment = segments[i]; + break; + } + } + + bool is_collection = true; + if (last_segment->segment_type() == odata_path_segment_type::Singleton) + { + is_collection = false; + } + else if (last_segment->segment_type() == odata_path_segment_type::NavigationProperty) + { + auto navigation_type = last_segment->as()->navigation_type()->get_navigation_type(); + if (navigation_type->get_type_kind() == edm_type_kind_t::Entity) + { + is_collection = false; + } + } + + auto key_string = is_collection ? entity_value->get_entity_key_string() : U(""); + entity_value->set_edit_link(navigation_source + key_string); + + // check to see if it is an derived type + if (expect_type_name != entity_value->get_value_type()->get_name()) + { + ::odata::utility::string_t derived_edit_link = navigation_source + key_string; + derived_edit_link += U("/"); + if (entity_value->has_property(PAYLOAD_ANNOTATION_TYPE)) + { + ::odata::utility::string_t derived_type; + entity_value->try_get(PAYLOAD_ANNOTATION_TYPE, derived_type); + if (!derived_type.empty() && derived_type[0] == U('#')) + derived_type = derived_type.substr(1, derived_type.length() - 1); + derived_edit_link += derived_type; + entity_value->set_edit_link(derived_edit_link); + } + } +} + +std::shared_ptr odata_json_reader_minimal::prepare_for_reading(const odata::utility::json::value& content, std::shared_ptr expected_type = nullptr) +{ + ::odata::utility::string_t context_url; + if (content.has_field(U("@odata.context"))) + { + odata::utility::json::value annotation_value = content.at(U("@odata.context")); + context_url = annotation_value.is_null() ? U("") : annotation_value.as_string(); + } + + std::shared_ptr context_url_parsed_type; + if (!context_url.empty()) + { + auto context_url_parser = entity_factory::create_context_url_parser(m_model, m_service_root_url); + context_url_parsed_type = context_url_parser->get_payload_content_type(context_url); + } + + std::shared_ptr payload_type; + if (m_is_reading_response) //if it's response message + { + //Either the entity type is specified for reading, or the context url is present in payload. + + if (context_url.empty() || !context_url_parsed_type) + { + throw std::runtime_error("Failed to parse context url in payload"); + } + + if (expected_type) + { + //TODO: verify the type parsed from context url + + payload_type = expected_type; + } + else + { + payload_type = context_url_parsed_type; + } + } + else + { + //Must specify entity type for reading + if (!expected_type) + { + throw std::runtime_error("Entity type is not specified for reading message"); + } + + payload_type = expected_type; + } + + return payload_type; +} + +std::shared_ptr odata_json_reader_minimal::deserilize_entity_collection(const odata::utility::json::value& content, std::shared_ptr entity_set) +{ + std::shared_ptr collection_type; + if (entity_set) + { + collection_type = std::make_shared(entity_set->get_entity_type()); + } + std::shared_ptr resolved_type = prepare_for_reading(content, collection_type); + + auto resolved_collection_type = std::dynamic_pointer_cast(resolved_type); + if (!resolved_collection_type || resolved_collection_type->get_type_kind() != edm_type_kind_t::Collection) + { + throw std::runtime_error("the resolved type must be a collection type"); + } + + auto element_type = resolved_collection_type->get_element_type(); + if (!element_type || element_type->get_type_kind() != edm_type_kind_t::Entity) + { + throw std::runtime_error("the element type must be an entity type"); + } + + if (!content.has_field(U("value"))) + { + throw std::runtime_error("invalid payload format"); + } + + auto collection_value = handle_extract_collection_property(resolved_collection_type, content.at(U("value"))); + + if (m_is_reading_response) + { + ::odata::utility::string_t context_url = content.at(U("@odata.context")).as_string(); + + ::odata::utility::string_t navigation_source = get_navigation_source_from_context_url(context_url); + navigation_source = m_service_root_url + U("/") + navigation_source; + + set_edit_link_for_entity_collection_value(collection_value, element_type->get_name(), navigation_source); + + if (content.has_field(U("@odata.nextLink"))) + { + auto annotation_value = content.at(U("@odata.nextLink")); + collection_value->set_next_link(annotation_value.is_null() ? U("") : annotation_value.as_string()); + } + } + + collection_value->set_is_top_level(true); + return collection_value; +} + +std::shared_ptr odata_json_reader_minimal::deserilize_entity_value(const odata::utility::json::value& content, std::shared_ptr entity_type) +{ + std::shared_ptr resolved_type = prepare_for_reading(content, entity_type); + + auto resolved_entity_type = std::dynamic_pointer_cast(resolved_type); + if (!resolved_entity_type || resolved_entity_type->get_type_kind() != edm_type_kind_t::Entity) + { + throw std::runtime_error("the resolved type must be an entity type"); + } + + auto entity_value = handle_extract_entity_property(content, resolved_entity_type); + + if (m_is_reading_response) + { + ::odata::utility::string_t context_url = content.at(U("@odata.context")).as_string(); + + ::odata::utility::string_t navigation_source = get_navigation_source_from_context_url(context_url); + navigation_source = m_service_root_url + U("/") + navigation_source; + + set_edit_link_for_entity_value(entity_value, resolved_entity_type->get_name(), navigation_source); + } + + entity_value->set_is_top_level(true); + return entity_value; +} + +std::shared_ptr odata_json_reader_minimal::deserilize_property(const odata::utility::json::value& content, std::shared_ptr edm_type) +{ + std::shared_ptr resolved_type = prepare_for_reading(content, edm_type); + if (!resolved_type) + { + throw std::runtime_error("cannot resolve type for the payload"); + } + + switch (resolved_type->get_type_kind()) + { + case edm_type_kind_t::Primitive: + { + auto primitive_type = std::dynamic_pointer_cast(resolved_type); + if (!content.has_field(U("value"))) + { + throw std::runtime_error("invalid payload format"); + } + auto primitive_value = std::make_shared(primitive_type, strip_string(content.at(U("value")).serialize())); + + primitive_value->set_is_top_level(true); + return primitive_value; + } + break; + case edm_type_kind_t::Complex: + { + auto complex_type = std::dynamic_pointer_cast(resolved_type); + + auto complex_value = handle_extract_complex_property(content, complex_type); + + complex_value->set_is_top_level(true); + return complex_value; + } + break; + case edm_type_kind_t::Enum: + { + auto enum_type = std::dynamic_pointer_cast(resolved_type); + if (!content.has_field(U("value"))) + { + throw std::runtime_error("invalid payload format"); + } + auto enum_value = std::make_shared(enum_type, strip_string(content.at(U("value")).serialize())); + + enum_value->set_is_top_level(true); + return enum_value; + } + break; + case edm_type_kind_t::Collection: + { + auto collection_type = std::dynamic_pointer_cast(resolved_type); + auto element_type = collection_type->get_element_type(); + auto element_type_kind = element_type->get_type_kind(); + if (element_type_kind != edm_type_kind_t::Primitive && element_type_kind != edm_type_kind_t::Enum && element_type_kind != edm_type_kind_t::Complex) + { + throw std::runtime_error("the resolved type is not a property type"); + } + if (!content.has_field(U("value"))) + { + throw std::runtime_error("invalid payload format"); + } + auto collection_value = handle_extract_collection_property(collection_type, content.at(U("value"))); + + collection_value->set_is_top_level(true); + return collection_value; + } + break; + default: + { + throw std::runtime_error("cannot resolve the expected type"); + } + } +} + +std::shared_ptr odata_json_reader_minimal::deserilize(const odata::utility::json::value& content) +{ + auto return_type = prepare_for_reading(content); + ::odata::utility::string_t context_url = content.at(U("@odata.context")).as_string(); + + std::shared_ptr return_value; + + if (!return_type) + { + return return_value; + } + + ::odata::utility::string_t edit_link = get_navigation_source_from_context_url(context_url); + edit_link = m_service_root_url + U("/") + edit_link; + + auto return_type_kind = return_type->get_type_kind(); + + if (return_type_kind == edm_type_kind_t::Collection) + { + if (!content.has_field(U("value"))) + { + throw std::runtime_error("invalid payload format"); + } + + auto collection_value = handle_extract_collection_property(return_type, content.at(U("value"))); + set_edit_link_for_entity_collection_value(collection_value, std::dynamic_pointer_cast(return_type)->get_element_type()->get_name(), edit_link); + + if (content.has_field(U("@odata.nextLink"))) + { + odata::utility::json::value annotation_value = content.at(U("@odata.nextLink")); + collection_value->set_next_link(annotation_value.is_null() ? U("") : annotation_value.as_string()); + } + + return_value = collection_value; + } + else if (return_type_kind == edm_type_kind_t::Entity) + { + auto entity_return_type = std::dynamic_pointer_cast(return_type); + + auto entity_value = handle_extract_entity_property(content, entity_return_type); + + // calculate edit link if necessary + if (entity_value) + { + set_edit_link_for_entity_value(entity_value, return_type->get_name(), edit_link); + } + + return_value = entity_value; + } + else if (return_type_kind == edm_type_kind_t::Primitive) + { + if (!content.has_field(U("value"))) + { + throw std::runtime_error("invalid payload format"); + } + auto primitive_value = std::make_shared(return_type, strip_string(content.at(U("value")).serialize())); + + return_value = primitive_value; + } + else if (return_type_kind == edm_type_kind_t::Complex) + { + auto complex_return_type = std::dynamic_pointer_cast(return_type); + + return_value = handle_extract_complex_property(content, complex_return_type); + } + else if (return_type_kind == edm_type_kind_t::Enum) + { + if (!content.has_field(U("value"))) + { + throw std::runtime_error("invalid payload format"); + } + auto enum_value = std::make_shared(return_type, strip_string(content.at(U("value")).serialize())); + + return_value = enum_value; + } + + return_value->set_is_top_level(true); + return return_value; +} + +std::shared_ptr odata_json_reader_minimal::handle_extract_navigation_property(const odata::utility::json::value& value, std::shared_ptr navigation_type) +{ + if (!navigation_type) + { + return nullptr; + } + + switch(value.type()) + { + case json::value::Array: + { + auto collection = std::dynamic_pointer_cast<::odata::edm::edm_collection_type>(navigation_type->get_navigation_type()); + + if (collection) + { + return handle_extract_collection_property(collection, value); + } + } + break; + case json::value::Object: + { + auto navi_entity_type = std::dynamic_pointer_cast<::odata::edm::edm_entity_type>(navigation_type->get_navigation_type()); + + return handle_extract_entity_property(value, navi_entity_type); + } + break; + default: + break; + } + + return nullptr; +} + +std::shared_ptr odata_json_reader_minimal::handle_extract_entity_property(const odata::utility::json::value& value, std::shared_ptr& entity_type) +{ + if (!entity_type) + { + return nullptr; + } + + auto ret_value = std::make_shared(entity_type); + + // find odata.type and check odata.type to see if it is a derived type + if (value.has_field(PAYLOAD_ANNOTATION_TYPE)) + { + auto annotation_type = value.at(PAYLOAD_ANNOTATION_TYPE); + auto annotation_value = annotation_type.as_string(); + ret_value->set_value(PAYLOAD_ANNOTATION_TYPE, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_TYPE), annotation_value)); + annotation_value = annotation_value.substr(1, annotation_value.length() - 1); + auto ret_entity_type = m_model->find_entity_type(annotation_value); + if (ret_entity_type) + { + entity_type = ret_entity_type; + ret_value->set_value_type(entity_type); + } + } + + if (value.has_field(PAYLOAD_ANNOTATION_EDITLINK)) + { + auto annotation_type = value.at(PAYLOAD_ANNOTATION_EDITLINK); + auto annotation_value = annotation_type.as_string(); + if (::odata::utility::is_relative_path(m_service_root_url, annotation_value)) + { + annotation_value = m_service_root_url + U("/") + annotation_value; + } + ret_value->set_edit_link(annotation_value); + } + + odata::utility::json::value original_value = value; + + for (auto iter = value.as_object().begin(); iter != value.as_object().end(); ++iter) + { + auto name = iter->first; + auto& value = iter->second; + + auto index = name.find(U("odata.")); + if (index == ::odata::utility::string_t::npos) + { + auto prop = entity_type->find_property(name); + if (prop && prop->get_property_type() && prop->get_property_type()->get_type_kind() == edm_type_kind_t::Navigation) + { + // process navigation field + auto navigation_type = std::dynamic_pointer_cast(prop->get_property_type()); + if (navigation_type) + { + ::odata::utility::string_t context_url_property = name + U("@odata.context"); + ::odata::utility::string_t edit_link; + if (original_value.has_field(context_url_property)) + { + edit_link = original_value[context_url_property].as_string(); + edit_link = get_navigation_source_from_context_url(edit_link); + } + else if (!navigation_type->is_contained()) + { + // not a contained navigation property + auto binded_source = navigation_type->get_binded_navigation_source(); + if (binded_source) + { + edit_link = binded_source->get_name(); + } + } + + auto navigation_value = handle_extract_navigation_property(value, navigation_type); + ret_value->set_value(name, navigation_value); + + if (edit_link.empty() || !m_is_reading_response) + { + continue; + } + edit_link = m_service_root_url + U("/") + edit_link; + + + // set edit_link + if (navigation_value->get_value_type()->get_type_kind() == edm_type_kind_t::Collection) + { + auto collection_value = std::dynamic_pointer_cast(navigation_value); + + if (collection_value) + { + set_edit_link_for_entity_collection_value(collection_value, collection_value->get_value_type()->get_name(), edit_link); + } + } + else if (navigation_value->get_value_type()->get_type_kind() == edm_type_kind_t::Entity) + { + auto element_value = std::dynamic_pointer_cast(navigation_value); + if (element_value) + { + set_edit_link_for_entity_value(element_value, element_value->get_value_type()->get_name(), edit_link); + } + } + } + } + else + { + // Process fields that are not annotations + switch(value.type()) + { + case json::value::Array: + { + // A collection + auto collection_prop = entity_type->find_property(name); + if (!collection_prop) + { + continue; + } + + ret_value->set_value(name, handle_extract_collection_property(collection_prop->get_property_type(), value)); + } + break; + case json::value::Object: + { + // A complex + auto complex_prop = entity_type->find_property(name); + if (!complex_prop) + { + // to do maybe the element is an entity + continue; + } + + auto a = complex_prop->get_property_type(); + auto b = a->get_name(); + + auto ct_type = m_model->find_complex_type(complex_prop->get_property_type()->get_name()); + if (ct_type) + { + ret_value->set_value(name, handle_extract_complex_property(value, ct_type)); + } + } + break; + case json::value::Null: + { + auto value_prop = entity_type->find_property(name); + if (!value_prop) + { + continue; + } + + std::shared_ptr null_odata_value;; + ret_value->set_value(name, null_odata_value); + } + break; + default: + { + auto primitive_prop = entity_type->find_property(name); + if (!primitive_prop) + { + continue; + } + + if (primitive_prop->get_property_type()->get_type_kind() == Enum) + { + ret_value->set_value(name, std::make_shared(primitive_prop->get_property_type(), strip_string(value.serialize()))); + } + else + { + ret_value->set_value(name, std::make_shared(primitive_prop->get_property_type(), strip_string(value.serialize()))); + } + } + break; + } + } + } + else + { + auto annotation = name.substr(index - 1); + if (annotation == PAYLOAD_ANNOTATION_NAVIGATIONLINK) + { + auto pname = name.substr(0, index - 1); + auto navigation_prop = entity_type->find_property(pname); + if (!navigation_prop) + { + continue; + } + + ret_value->set_value(name, std::make_shared(navigation_prop->get_property_type(), strip_string(value.serialize()))); + } + else + { + if (name.find(U("@")) == 0) + { + auto annotation = name.substr(index - 1); + auto annotation_value = strip_string(value.serialize()); + + if (annotation == PAYLOAD_ANNOTATION_READLINK) + { + ret_value->set_value(PAYLOAD_ANNOTATION_READLINK, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_READLINK), annotation_value)); + } + else if (annotation == PAYLOAD_ANNOTATION_ID) + { + ret_value->set_value(PAYLOAD_ANNOTATION_ID, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_ID), annotation_value)); + } + } + } + } + } + + return ret_value; +} + +std::shared_ptr odata_json_reader_minimal::handle_extract_complex_property(const ::odata::utility::json::value& value, std::shared_ptr<::odata::edm::edm_complex_type>& edm_complex_type) +{ + if (!edm_complex_type) + { + return nullptr; + } + + auto ret_value = std::make_shared(edm_complex_type); + + // find odata.type and check odata.type to see if it is a derived type + if (value.has_field(PAYLOAD_ANNOTATION_TYPE)) + { + auto annotation_type = value.at(PAYLOAD_ANNOTATION_TYPE); + auto annotation_value = annotation_type.as_string(); + ret_value->set_value(PAYLOAD_ANNOTATION_TYPE, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_TYPE), annotation_value)); + annotation_value = annotation_value.substr(1, annotation_value.length() - 1); + auto ret_complex_type = m_model->find_complex_type(annotation_value); + if (ret_complex_type) + { + edm_complex_type = ret_complex_type; + ret_value->set_value_type(edm_complex_type); + } + } + + for (auto iter = value.as_object().begin(); iter != value.as_object().end(); ++iter) + { + auto name = iter->first; + auto& value = iter->second; + + auto index = name.find(U("odata.")); + if (index == ::odata::utility::string_t::npos) + { + // Process fields that are not annotations + switch(value.type()) + { + case json::value::Array: + { + // A collection + auto collection_prop = edm_complex_type->find_property(name); + if (!collection_prop) + { + continue; + } + + ret_value->set_value(name, handle_extract_collection_property(collection_prop->get_property_type(), value)); + } + break; + case json::value::Object: + { + // A complex + auto complext_prop = edm_complex_type->find_property(name); + if (!complext_prop) + { + continue; + } + + auto ct_type = m_model->find_complex_type(complext_prop->get_property_type()->get_name()); + if (ct_type) + { + ret_value->set_value(name, handle_extract_complex_property(value, ct_type)); + } + + } + break; + case json::value::Null: + { + auto value_prop = edm_complex_type->find_property(name); + if (!value_prop) + { + // to do maybe the element is an entity + continue; + } + + ret_value->set_value(name, (std::nullptr_t)nullptr); + } + break; + default: + { + auto primitive_prop = edm_complex_type->find_property(name); + if (!primitive_prop) + { + continue; + } + + if (primitive_prop->get_property_type()->get_type_kind() == Enum) + { + ret_value->set_value(name, std::make_shared(primitive_prop->get_property_type(), strip_string(value.serialize()))); + } + else + { + ret_value->set_value(name, std::make_shared(primitive_prop->get_property_type(), strip_string(value.serialize()))); + } + } + break; + } + } + else + { + if (name.find(U("@")) == 0) + { + auto annotation = name.substr(index - 1); + auto annotation_value = strip_string(value.serialize()); + + if (annotation == PAYLOAD_ANNOTATION_READLINK) + { + ret_value->set_value(PAYLOAD_ANNOTATION_READLINK, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_READLINK), annotation_value)); + } + else if (annotation == PAYLOAD_ANNOTATION_ID) + { + ret_value->set_value(PAYLOAD_ANNOTATION_ID, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_ID), annotation_value)); + } + } + + } + } + + return ret_value; +} + +std::shared_ptr odata_json_reader_minimal::handle_extract_collection_property(std::shared_ptr type, const odata::utility::json::value& value) +{ + if (!type) + { + return nullptr; + } + + // get elements of collection + auto p_edm_collection_type = std::dynamic_pointer_cast(type); + if (!p_edm_collection_type) + { + return nullptr; + } + + auto element_type = p_edm_collection_type->get_element_type(); + if (!element_type) + { + return nullptr; + } + + auto p_collection_property = std::make_shared(type); + + for (auto iter = value.as_array().begin(); iter != value.as_array().end(); iter++) + { + odata::utility::json::value element_value = *iter; + + if (element_type->get_type_kind() == edm_type_kind_t::Primitive) + { + p_collection_property->add_collection_value(std::make_shared(element_type, strip_string(element_value.serialize()))); + } + else if (element_type->get_type_kind() == edm_type_kind_t::Complex) + { + auto ct_type = m_model->find_complex_type(element_type->get_name()); + if (ct_type) + { + p_collection_property->add_collection_value(handle_extract_complex_property(element_value, ct_type)); + } + } + else if (element_type->get_type_kind() == edm_type_kind_t::Entity) + { + auto entity_element_type = std::dynamic_pointer_cast(element_type); + + p_collection_property->add_collection_value(handle_extract_entity_property(element_value, entity_element_type)); + } + else if (element_type->get_type_kind() == edm_type_kind_t::Enum) + { + p_collection_property->add_collection_value(std::make_shared(element_type, strip_string(element_value.serialize()))); + } + else + { + throw std::runtime_error("Can't parse odata_payload contains collection of collection!"); + } + } + + return p_collection_property; +} + +void odata_json_reader_minimal::handle_extract_entity_annotation(const ::odata::utility::string_t& annotation, const ::odata::utility::string_t& value, std::shared_ptr& entity_value) +{ + if (annotation == PAYLOAD_ANNOTATION_READLINK) + { + entity_value->set_value(PAYLOAD_ANNOTATION_READLINK, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_READLINK), value)); + } + else if (annotation == PAYLOAD_ANNOTATION_ID) + { + entity_value->set_value(PAYLOAD_ANNOTATION_ID, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_ID), value)); + } + else if (annotation == PAYLOAD_ANNOTATION_TYPE) + { + entity_value->set_value(PAYLOAD_ANNOTATION_TYPE, std::make_shared(std::make_shared(PAYLOAD_ANNOTATION_TYPE), value)); + } +} + +}} \ No newline at end of file diff --git a/src/core/odata_json_writer.cpp b/src/core/odata_json_writer.cpp new file mode 100644 index 0000000..dc18564 --- /dev/null +++ b/src/core/odata_json_writer.cpp @@ -0,0 +1,414 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_json_writer.h" + +using namespace ::odata::utility; +using namespace ::odata::edm; + +namespace odata { namespace core +{ + odata::utility::json::value odata_json_writer::serialize(const std::shared_ptr service_document) + { + odata::utility::json::value result = odata::utility::json::value::object(); + + // context uri + result[ANNOTATION_ODATA_CONTEXT] = odata::utility::json::value::string(m_metadata_document_uri.to_string()); + + odata::utility::json::value value = odata::utility::json::value::array(service_document->size()); + + size_t index = 0; + for (auto iter = service_document->cbegin(); iter != service_document->cend(); iter++) + { + value[index++] = serialize(*iter); + } + + result[JSON_VALUE] = value; + + return result; + } + + odata::utility::json::value odata_json_writer::serialize(std::shared_ptr value_object) + { + if (value_object) + { + return serialize_odata_value(value_object->get_value_type(), value_object); + } + + return odata::utility::json::value::null(); + } + + ::odata::utility::json::value odata_json_writer::serialize(std::vector> parameters) + { + if (parameters.empty()) + { + return odata::utility::json::value::null(); + } + + odata::utility::json::value result = odata::utility::json::value::object(); + for(auto iter = parameters.cbegin(); iter != parameters.cend(); iter++) + { + result[(*iter)->get_name()] = serialize((*iter)->get_value()); + } + + return result; + } + + ::odata::utility::json::value odata_json_writer::serialize_odata_value(const std::shared_ptr& property_type, const std::shared_ptr& property_value) + { + odata::utility::json::value result = odata::utility::json::value::object(); + + if (!property_type || !property_value) + { + return result; + } + + switch(property_type->get_type_kind()) + { + case edm_type_kind_t::Primitive: + { + auto p_value = std::dynamic_pointer_cast(property_value); + auto p_primitive_type = std::dynamic_pointer_cast(property_type); + return serialize_primitive_value(p_primitive_type, p_value); + } + break; + case edm_type_kind_t::Enum: + { + auto p_value = std::dynamic_pointer_cast(property_value); + return serialize_enum_value(p_value); + } + break; + case edm_type_kind_t::PayloadAnnotation: + { + auto p_value = std::dynamic_pointer_cast(property_value); + return odata::utility::json::value::string(p_value->to_string()); + } + break; + case edm_type_kind_t::Collection: + { + auto p_value = std::dynamic_pointer_cast(property_value); + return serialize_collection_value(p_value); + } + break; + case edm_type_kind_t::Complex: + case edm_type_kind_t::Entity: + { + auto p_value = std::dynamic_pointer_cast(property_value); + return seriliaze_structured_value(p_value); + } + break; + default: + { + throw std::runtime_error("write unsupported property type!"); + } + break; + } + } + + ::odata::utility::json::value odata_json_writer::serialize_primitive_value(const std::shared_ptr& p_primitive_type, const std::shared_ptr& p_value) + { + odata::utility::json::value result = odata::utility::json::value::object(); + if (!p_primitive_type || !p_value) + { + return result; + } + + switch(p_primitive_type->get_primitive_kind()) + { + case edm_primitive_type_kind_t::Boolean: + return ::odata::utility::json::value::boolean(p_value->as()); + case edm_primitive_type_kind_t::Double: + return ::odata::utility::json::value::number(p_value->as()); + case edm_primitive_type_kind_t::Int32: + return ::odata::utility::json::value::number(p_value->as()); + case edm_primitive_type_kind_t::Int16: + return ::odata::utility::json::value::number(p_value->as()); + case edm_primitive_type_kind_t::Int64: + return ::odata::utility::json::value(p_value->as()); + case edm_primitive_type_kind_t::Single: + return ::odata::utility::json::value(p_value->as()); + case edm_primitive_type_kind_t::SByte: + case edm_primitive_type_kind_t::Guid: + case edm_primitive_type_kind_t::Binary: + case edm_primitive_type_kind_t::DateTimeOffset: + case edm_primitive_type_kind_t::Duration: + case edm_primitive_type_kind_t::String: + { + return ::odata::utility::json::value::string(p_value->to_string()); + } + break; + case edm_primitive_type_kind_t::Stream: + { + throw std::runtime_error("stream primitive value not implemented!"); + } + break; + default: + { + throw std::runtime_error("unknown value!"); + } + break; + } + } + + ::odata::utility::json::value odata_json_writer::seriliaze_structured_value(const std::shared_ptr& p_value) + { + odata::utility::json::value result = odata::utility::json::value::object(); + if (!p_value) + { + return result; + } + + if(!p_value->get_context_url().is_empty()) + { + result[ANNOTATION_ODATA_CONTEXT] = odata::utility::json::value::string(p_value->get_context_url().to_string()); + } + + if(!p_value->get_type_name().empty()) + { + result[ANNOTATION_ODATA_TYPE] = odata::utility::json::value::string(p_value->get_type_name()); + } + + auto p_entity_value = std::dynamic_pointer_cast(p_value); + if (p_entity_value && !p_entity_value->get_etag().empty()) + { + result[ANNOTATION_ODATA_ETAG] = odata::utility::json::value::string(p_entity_value->get_etag()); + } + + if (p_entity_value && !p_entity_value->get_id().is_empty()) + { + result[ANNOTATION_ODATA_ID] = odata::utility::json::value::string(p_entity_value->get_id().to_string()); + } + + if (p_entity_value && !p_entity_value->get_edit_link().is_empty()) + { + result[ANNOTATION_ODATA_EDIT_LINK] = odata::utility::json::value::string(p_entity_value->get_edit_link().to_string()); + } + + if (p_entity_value && !p_entity_value->get_read_link().is_empty()) + { + result[ANNOTATION_ODATA_READ_LINK] = odata::utility::json::value::string(p_entity_value->get_read_link().to_string()); + } + + for (auto iter = p_value->properties().cbegin(); iter != p_value->properties().cend(); iter++) + { + if (!iter->second) + { + result[iter->first] = odata::utility::json::value::null(); + } + else + { + auto property_type = iter->second->get_value_type(); + + if (!is_type_serializable(property_type)) + { + continue; + } + result[iter->first] = serialize_odata_value(property_type, iter->second); + } + + } + + for (auto iter = p_value->get_actions().cbegin(); iter != p_value->get_actions().cend(); iter++) + { + odata::utility::json::value action = odata::utility::json::value::object(); + action[U("title")] = odata::utility::json::value::string((*iter)->get_title()); + action[U("target")] = odata::utility::json::value::string((*iter)->get_target().to_string()); + result[U("#") + (*iter)->get_name()] = action; + } + + for (auto iter = p_value->get_functions().cbegin(); iter != p_value->get_functions().cend(); iter++) + { + odata::utility::json::value function = odata::utility::json::value::object(); + function[U("title")] = odata::utility::json::value::string((*iter)->get_title()); + function[U("target")] = odata::utility::json::value::string((*iter)->get_target().to_string()); + result[U("#") + (*iter)->get_name()] = function; + } + + for (auto iter = p_value->get_instance_annotations().cbegin(); iter != p_value->get_instance_annotations().cend(); iter++) + { + odata::utility::json::value annotation = odata::utility::json::value::object(); + annotation[U("#") + iter->first] = serialize(iter->second); + } + + return result; + } + + odata::utility::json::value odata_json_writer::serialize_enum_value(const std::shared_ptr& p_value) + { + if (!p_value) + { + return odata::utility::json::value::null(); + } + + return odata::utility::json::value::string(p_value->to_string()); + } + + odata::utility::json::value odata_json_writer::serialize_collection_value(const std::shared_ptr& p_value) + { + if (!p_value) + { + return odata::utility::json::value::null(); + } + + odata::utility::json::value result = odata::utility::json::value::object(); + + if(!p_value->get_context_url().is_empty()) + { + result[ANNOTATION_ODATA_CONTEXT] = odata::utility::json::value::string(p_value->get_context_url().to_string()); + } + + if(p_value->get_count().has_value()) + { + result[ANNOTATION_ODATA_COUNT] = odata::utility::json::value::number((double)p_value->get_count().value()); + } + + std::shared_ptr collection_type = std::dynamic_pointer_cast(p_value->get_value_type()); + auto element_type = collection_type->get_element_type(); + + std::vector values = std::vector(); + + for (auto iter = p_value->get_collection_values().cbegin(); iter != p_value->get_collection_values().cend(); iter++) + { + if (!is_type_serializable(element_type)) + { + continue; + } + + values.push_back(serialize_odata_value(element_type, *iter)); + } + + odata::utility::json::value collection_json = odata::utility::json::value::array(values); + result[U("value")] = collection_json; + + if(!p_value->get_next_link().is_empty()) + { + result[ANNOTATION_ODATA_NEXT_LINK] = odata::utility::json::value::string(p_value->get_next_link().to_string()); + } + + if(!p_value->get_delta_link().is_empty()) + { + result[ANNOTATION_ODATA_DELTA_LINK] = odata::utility::json::value::string(p_value->get_delta_link().to_string()); + } + + if (p_value->is_top_level()) + { + return result; + } + else + { + return collection_json; + } + return result; + } + + bool odata_json_writer::is_type_serializable(const std::shared_ptr& property_type) + { + if (property_type) + { + if (property_type->get_type_kind() == edm_type_kind_t::Collection + || property_type->get_type_kind() == edm_type_kind_t::Entity + || property_type->get_type_kind() == edm_type_kind_t::Complex + || property_type->get_type_kind() == edm_type_kind_t::Primitive + || property_type->get_type_kind() == edm_type_kind_t::Enum + || (property_type->get_type_kind() == edm_type_kind_t::PayloadAnnotation) + && (property_type->get_name() == PAYLOAD_ANNOTATION_TYPE || property_type->get_name() == PAYLOAD_ANNOTATION_ID)) + { + return true; + } + } + + return false; + } + + odata::utility::json::value odata_json_writer::serialize(std::shared_ptr service_document_element) + { + odata::utility::json::value result = odata::utility::json::value::object(); + result[JSON_NAME] = odata::utility::json::value::string(service_document_element->get_name()); + + ::odata::utility::string_t kind; + switch(service_document_element->get_kind()) + { + case ENTITY_SET: + kind = JSON_ENTITYSET + break; + case SINGLETON: + kind = JSON_SINGLETON + break; + case FUNCTION_IMPORT: + kind = JSON_FUNCTIONIMPORT + break; + } + result[JSON_KIND] = odata::utility::json::value::string(kind); + result[JSON_URI] = odata::utility::json::value::string(service_document_element->get_url()); + + return result; + } + + odata::utility::json::value odata_json_writer::serialize(std::shared_ptr error) + { + odata::utility::json::value result = odata::utility::json::value::object(); + odata::utility::json::value error_json = odata::utility::json::value::object(); + error_json[U("code")] = odata::utility::json::value::string(error->get_code()); + error_json[U("message")] = odata::utility::json::value::string(error->get_message()); + error_json[U("target")] = odata::utility::json::value::string(error->get_target()); + result[U("error")] = error_json; + return result; + } + + odata::utility::json::value odata_json_writer::serialize(std::shared_ptr entity_reference) + { + odata::utility::json::value result = odata::utility::json::value::object(); + + if(!entity_reference->get_context_url().is_empty()) + { + result[ANNOTATION_ODATA_CONTEXT] = odata::utility::json::value::string(entity_reference->get_context_url().to_string()); + } + + if (!entity_reference->get_id().is_empty()) + { + result[ANNOTATION_ODATA_ID] = odata::utility::json::value::string(entity_reference->get_id().to_string()); + } + + return result; + } + + odata::utility::json::value odata_json_writer::serialize(std::shared_ptr entity_reference_collection) + { + odata::utility::json::value result = odata::utility::json::value::object(); + + if(!entity_reference_collection->get_context_url().is_empty()) + { + result[ANNOTATION_ODATA_CONTEXT] = odata::utility::json::value::string(entity_reference_collection->get_context_url().to_string()); + } + + if(!entity_reference_collection->get_next_link().is_empty()) + { + result[ANNOTATION_ODATA_NEXT_LINK] = odata::utility::json::value::string(entity_reference_collection->get_next_link().to_string()); + } + + if(!entity_reference_collection->get_delta_link().is_empty()) + { + result[ANNOTATION_ODATA_DELTA_LINK] = odata::utility::json::value::string(entity_reference_collection->get_delta_link().to_string()); + } + + if(entity_reference_collection->get_count().has_value()) + { + result[ANNOTATION_ODATA_COUNT] = odata::utility::json::value::number((double)entity_reference_collection->get_count().value()); + } + + odata::utility::json::value value_json = odata::utility::json::value::array(entity_reference_collection->size()); + size_t index = 0; + for (auto iter = entity_reference_collection->cbegin(); iter != entity_reference_collection->cend(); iter++) + { + value_json[index++] = serialize(*iter); + } + + result[JSON_VALUE] = value_json; + + return result; + + } +}} \ No newline at end of file diff --git a/src/core/odata_message_reader.cpp b/src/core/odata_message_reader.cpp new file mode 100644 index 0000000..e18bb62 --- /dev/null +++ b/src/core/odata_message_reader.cpp @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_message_reader.h" +#include "odata/core/odata_json_reader_minimal.h" + +using namespace ::odata::core; + +namespace odata { namespace core +{ + std::shared_ptr odata_message_reader::read_odata_value() + { + auto json_reader = std::make_shared(m_model, m_base_uri.to_string(), m_is_response_message); + return json_reader->deserilize(::odata::utility::json::value::parse(m_message_body)); + } + + std::shared_ptr odata_message_reader::read_entity_value(std::shared_ptr<::odata::edm::edm_entity_type> edm_type) + { + auto json_reader = std::make_shared(m_model, m_base_uri.to_string(), m_is_response_message); + return json_reader->deserilize_entity_value(::odata::utility::json::value::parse(m_message_body), edm_type); + } + + std::shared_ptr odata_message_reader::read_entity_value() + { + return read_entity_value(nullptr); + } + + std::shared_ptr odata_message_reader::read_entity_collection(std::shared_ptr<::odata::edm::edm_entity_set> entity_set) + { + auto json_reader = std::make_shared(m_model, m_base_uri.to_string(), m_is_response_message); + return json_reader->deserilize_entity_collection(::odata::utility::json::value::parse(m_message_body), entity_set); + } + + std::shared_ptr<::odata::core::odata_collection_value> odata_message_reader::read_entity_collection() + { + return read_entity_collection(nullptr); + } + + std::shared_ptr odata_message_reader::read_property(std::shared_ptr<::odata::edm::edm_named_type> edm_type) + { + auto json_reader = std::make_shared(m_model, m_base_uri.to_string(), m_is_response_message); + return json_reader->deserilize_property(::odata::utility::json::value::parse(m_message_body), edm_type); + } + + std::shared_ptr odata_message_reader::read_property() + { + return read_property(nullptr); + } +}} \ No newline at end of file diff --git a/src/core/odata_message_writer.cpp b/src/core/odata_message_writer.cpp new file mode 100644 index 0000000..c06f4a5 --- /dev/null +++ b/src/core/odata_message_writer.cpp @@ -0,0 +1,104 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_message_writer.h" + +using namespace odata::utility::json; +using namespace odata::edm; + +namespace odata { namespace core +{ + ::odata::utility::string_t odata_message_writer::write_service_document(const std::shared_ptr service_document) + { + auto writer = std::make_shared<::odata::core::odata_json_writer>(m_model, m_service_root); + return writer->serialize(service_document).to_string(); + } + + ::odata::utility::string_t odata_message_writer::write_metadata_document() + { + std::ostringstream outstream;; + std::shared_ptr writer = std::make_shared(outstream); + writer->write_model(m_model); + std::string str = outstream.str(); + + ::odata::utility::string_t str_t; + str_t.assign(str.begin(), str.end()); + + return str_t; + } + + ::odata::utility::string_t odata_message_writer::write_odata_value(std::shared_ptr<::odata::core::odata_value> value) + { + auto writer = std::make_shared<::odata::core::odata_json_writer>(m_model); + auto value_context = writer->serialize(value); + auto ss = value_context.serialize(); + return ss; + } + + ::odata::utility::string_t odata_message_writer::write_asynchronous_odata_value(std::shared_ptr<::odata::core::odata_value> value, int16_t status_code, ::odata::utility::string_t status_message, std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> headers) + { + ::odata::utility::ostringstream_t stream; + stream << U("HTTP/1.1 ") << status_code << U(" ") << status_message << U("\n"); + for(auto header = headers.cbegin(); header != headers.cend(); header++) + { + stream << header->first << U(": ") <second << U("\n"); + } + + + stream << U("\n"); + stream << write_odata_value(value); + return stream.str(); + } + + ::odata::utility::string_t odata_message_writer::write_odata_batch_value(std::shared_ptr<::odata::core::odata_batch_value> batch_value) + { + ::odata::utility::ostringstream_t stream; + for(auto part = batch_value->cbegin(); part != batch_value->cend(); part++) + { + stream << U("--") << batch_value->get_boundary() << U("\n"); + stream << U("Content-Type: application/http"); + stream << U("Content-Transfer-Encoding: binary"); + stream << U("\n"); + + stream << U("HTTP/1.1 ") << (*part)->get_status_code() << U(" ") << (*part)->get_status_message() << U("\n"); + for(auto header = (*part)->get_headers().cbegin(); header != (*part)->get_headers().cend(); header++) + { + stream << header->first << U(": ") <second << U("\n"); + } + + stream << U("\n"); + + stream << write_odata_value((*part)->get_odata_value()); + } + + return stream.str(); + } + + ::odata::utility::string_t odata_message_writer::write_odata_error(std::shared_ptr<::odata::core::odata_error> error) + { + auto writer = std::make_shared<::odata::core::odata_json_writer>(m_model); + auto value_context = writer->serialize(error); + auto ss = value_context.serialize(); + return ss; + } + + ::odata::utility::string_t odata_message_writer::write_odata_entity_reference(std::shared_ptr<::odata::core::odata_entity_reference> entity_reference) + { + auto writer = std::make_shared<::odata::core::odata_json_writer>(m_model); + auto value_context = writer->serialize(entity_reference); + auto ss = value_context.serialize(); + return ss; + } + + ::odata::utility::string_t odata_message_writer::write_odata_entity_reference_collection(std::shared_ptr<::odata::core::odata_entity_reference_collection> entity_reference_collection) + { + auto writer = std::make_shared<::odata::core::odata_json_writer>(m_model); + auto value_context = writer->serialize(entity_reference_collection); + auto ss = value_context.serialize(); + return ss; + } + +}} \ No newline at end of file diff --git a/src/core/odata_path.cpp b/src/core/odata_path.cpp new file mode 100644 index 0000000..57ca47f --- /dev/null +++ b/src/core/odata_path.cpp @@ -0,0 +1,14 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_core.h" + +namespace odata { namespace core +{ + + + +}} \ No newline at end of file diff --git a/src/core/odata_path_segment.cpp b/src/core/odata_path_segment.cpp new file mode 100644 index 0000000..d10218e --- /dev/null +++ b/src/core/odata_path_segment.cpp @@ -0,0 +1,100 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_path_segment.h" + +namespace odata { namespace core +{ + +std::shared_ptr odata_path_segment::create_metadata_segment() +{ + static auto instance = std::make_shared(); + + return instance; +} + +std::shared_ptr odata_path_segment::create_batch_segment() +{ + static auto instance = std::make_shared(); + + return instance; +} + +std::shared_ptr odata_path_segment::create_entity_set_segment( + std::shared_ptr<::odata::edm::edm_entity_set> entity_set) +{ + return std::make_shared(entity_set); +} + +std::shared_ptr odata_path_segment::create_singleton_segment( + std::shared_ptr<::odata::edm::edm_singleton> singleton) +{ + return std::make_shared(singleton); +} + +std::shared_ptr odata_path_segment::create_key_segment( + std::shared_ptr<::odata::edm::edm_navigation_source> navigation_source, + std::shared_ptr<::odata::edm::edm_entity_type> target_entity_type, + std::vector>> keys) +{ + return std::make_shared(navigation_source, target_entity_type, std::move(keys)); +} + +std::shared_ptr odata_path_segment::create_structural_property_segment( + std::shared_ptr<::odata::edm::edm_structured_type> owning_type, + std::shared_ptr<::odata::edm::edm_property_type> property) +{ + return std::make_shared(owning_type, property); +} + +std::shared_ptr odata_path_segment::create_navigation_property_segment( + std::shared_ptr<::odata::edm::edm_structured_type> owning_type, + std::shared_ptr<::odata::edm::edm_property_type> property, + std::shared_ptr<::odata::edm::edm_navigation_type> navigation_type) +{ + return std::make_shared(owning_type, property, navigation_type); +} + +std::shared_ptr odata_path_segment::create_dynamic_property_segment(const ::odata::utility::string_t &property_name) +{ + return std::make_shared(property_name); +} + +std::shared_ptr odata_path_segment::create_value_segment() +{ + return std::make_shared(); +} + +std::shared_ptr odata_path_segment::create_count_segment() +{ + return std::make_shared(); +} + +std::shared_ptr odata_path_segment::create_ref_segment() +{ + return std::make_shared(); +} + +std::shared_ptr odata_path_segment::create_type_segment(std::shared_ptr<::odata::edm::edm_named_type> type) +{ + return std::make_shared(type); +} + +std::shared_ptr odata_path_segment::create_operation_import_segment( + std::shared_ptr<::odata::edm::edm_operation_import> operation_import, + std::vector>> parameters) +{ + return std::make_shared(operation_import, std::move(parameters)); +} + +std::shared_ptr odata_path_segment::create_operation_segment( + std::shared_ptr<::odata::edm::edm_operation_type> operation, + std::vector>> parameters) +{ + return std::make_shared(operation, std::move(parameters)); +} + +}} \ No newline at end of file diff --git a/src/core/odata_path_segment_visitor.cpp b/src/core/odata_path_segment_visitor.cpp new file mode 100644 index 0000000..9ed9e8d --- /dev/null +++ b/src/core/odata_path_segment_visitor.cpp @@ -0,0 +1,12 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_path_segment_visitor.h" + +namespace odata { namespace core +{ + +}} \ No newline at end of file diff --git a/src/core/odata_primitive_value.cpp b/src/core/odata_primitive_value.cpp new file mode 100644 index 0000000..dffb2a2 --- /dev/null +++ b/src/core/odata_primitive_value.cpp @@ -0,0 +1,98 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_core.h" +#include "odata/edm/edm_model_utility.h" + +using namespace ::odata::utility; +using namespace ::odata::edm; + +namespace odata { namespace core +{ + +void odata_primitive_value::_try_get(std::vector& value) const +{ + std::vector temp(::odata::utility::conversions::from_base64(to_string())); + value.swap(temp); +} + +void odata_primitive_value::_try_get(unsigned char& value) const +{ + const auto& propValue = to_string(); + + int16_t v; + utility::bind(propValue, v); + value = (unsigned char)v; +} + +void odata_primitive_value::_try_get(char& value) const +{ + const auto& propValue = to_string(); + + int32_t v; + utility::bind(propValue, v); + value = (char)v; +} + +void odata_primitive_value::_try_get(bool& value) const +{ + const auto& propValue = to_string(); + if (propValue == U("false") || propValue == U("0")) value = false; + else value = true; +} + +void odata_primitive_value::_try_get(::odata::utility::datetime& value) const +{ + value = ::odata::utility::datetime::from_string(to_string(), ::odata::utility::datetime::ISO_8601); +} + +void odata_primitive_value::_try_get(::odata::utility::seconds& value) const +{ + value = ::odata::utility::timespan::xml_duration_to_seconds(to_string()); +} + +void odata_primitive_value::_try_get(double& value) const +{ + const auto& propValue = to_string(); + utility::bind(propValue, value); +} + +void odata_primitive_value::_try_get(float& value) const +{ + const auto& propValue = to_string(); + utility::bind(propValue, value); +} + +void odata_primitive_value::_try_get(int16_t& value) const +{ + const auto& propValue = to_string(); + utility::bind(propValue, value); +} + +void odata_primitive_value::_try_get(int32_t& value) const +{ + const auto& propValue = to_string(); + utility::bind(propValue, value); +} + +void odata_primitive_value::_try_get(int64_t& value) const +{ + const auto& propValue = to_string(); + utility::bind(propValue, value); +} + +void odata_primitive_value::_try_get(uint64_t& value) const +{ + const auto& propValue = to_string(); + utility::bind(propValue, value); +} + +void odata_primitive_value::_try_get(::odata::utility::string_t& value) const +{ + value = to_string(); +} + +}} \ No newline at end of file diff --git a/src/core/odata_property_map.cpp b/src/core/odata_property_map.cpp new file mode 100644 index 0000000..3b9bc93 --- /dev/null +++ b/src/core/odata_property_map.cpp @@ -0,0 +1,318 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_core.h" +#include "odata/edm/edm_model_utility.h" + +using namespace ::odata::utility; +using namespace ::odata::edm; + +namespace odata { namespace core +{ + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, char& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); +#ifdef _UTF16_STRINGS + std::istringstream iss(::odata::utility::conversions::utf16_to_utf8(propValue)); +#else + std::istringstream iss(propValue); +#endif + iss >> value; + + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, unsigned char& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); +#ifdef _UTF16_STRINGS + std::istringstream iss(::odata::utility::conversions::utf16_to_utf8(propValue)); +#else + std::istringstream iss(propValue); +#endif + iss >> value; + + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, std::vector& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + std::vector temp(::odata::utility::conversions::from_base64(primitive_value->to_string())); + value.swap(temp); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, bool& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); + if (propValue == U("true") || propValue == U("1")) value = true; + else value = false; + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, ::odata::utility::datetime& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + value = ::odata::utility::datetime::from_string(primitive_value->to_string(), ::odata::utility::datetime::ISO_8601); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, ::odata::utility::seconds& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + value = ::odata::utility::timespan::xml_duration_to_seconds(primitive_value->to_string()); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, double& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); + utility::bind(propValue, value); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, float& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); + utility::bind(propValue, value); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, int16_t& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); + utility::bind(propValue, value); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, int32_t& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); + utility::bind(propValue, value); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, int64_t& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + const auto& propValue = primitive_value->to_string(); + utility::bind(propValue, value); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, ::odata::utility::string_t& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + auto primitive_value = std::dynamic_pointer_cast(found.second); + if (primitive_value) + { + value = primitive_value->to_string(); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, odata_property_map& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + std::shared_ptr p_complex_value = std::dynamic_pointer_cast(found.second); + if (p_complex_value) + { + value = p_complex_value->properties(); + return true; + } + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + value = found.second; + return true; + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + value = std::dynamic_pointer_cast(found.second); + return true; + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + value = std::dynamic_pointer_cast(found.second); + return true; + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + value = std::dynamic_pointer_cast(found.second); + return true; + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + value = std::dynamic_pointer_cast(found.second); + return true; + } + return false; +} + +bool odata_property_map::try_get(const ::odata::utility::string_t& property_name, std::shared_ptr& value) const +{ + auto found = find_property(property_name); + if (found.first) + { + value = std::dynamic_pointer_cast(found.second); + return true; + } + return false; +} + +std::pair> odata_property_map::find_property(const ::odata::utility::string_t& property_name) const + { + const auto& findIt = m_properties.find(property_name); + bool found = (findIt != m_properties.end()); + + if (found) + { + const auto& propDetails = findIt->second; + + if (!propDetails) + { + return make_pair(found, propDetails); + } + + return make_pair(found, propDetails); + } + + return make_pair(found, std::shared_ptr()); + } +}} \ No newline at end of file diff --git a/src/core/odata_query_node.cpp b/src/core/odata_query_node.cpp new file mode 100644 index 0000000..ae05690 --- /dev/null +++ b/src/core/odata_query_node.cpp @@ -0,0 +1,137 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_query_node.h" + +namespace odata { namespace core +{ + +std::shared_ptr odata_query_node::create_constant_node(std::shared_ptr<::odata::core::odata_primitive_value> value) +{ + return std::make_shared(value); +} + +std::shared_ptr odata_query_node::create_operator_or_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Or, left, right); +} + +std::shared_ptr odata_query_node::create_operator_and_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::And, left, right); +} + +std::shared_ptr odata_query_node::create_operator_eq_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Equal, left, right); +} + +std::shared_ptr odata_query_node::create_operator_ne_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::NotEqual, left, right); +} + +std::shared_ptr odata_query_node::create_operator_gt_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::GreaterThan, left, right); +} + +std::shared_ptr odata_query_node::create_operator_ge_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::GreaterThanOrEqual, left, right); +} + +std::shared_ptr odata_query_node::create_operator_lt_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::LessThan, left, right); +} + +std::shared_ptr odata_query_node::create_operator_le_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::LessThanOrEqual, left, right); +} + +std::shared_ptr odata_query_node::create_operator_has_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Has, left, right); +} + +std::shared_ptr odata_query_node::create_operator_add_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Add, left, right); +} + +std::shared_ptr odata_query_node::create_operator_sub_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Sub, left, right); +} + +std::shared_ptr odata_query_node::create_operator_mul_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Multiply, left, right); +} + +std::shared_ptr odata_query_node::create_operator_div_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Divide, left, right); +} + +std::shared_ptr odata_query_node::create_operator_mod_node(std::shared_ptr left, std::shared_ptr right) +{ + return std::make_shared(::odata::core::binary_operator_kind::Modulo, left, right); +} + +std::shared_ptr odata_query_node::create_operator_not_node(std::shared_ptr operand) +{ + return std::make_shared(::odata::core::unary_operator_kind::Not, operand); +} + +std::shared_ptr odata_query_node::create_operator_neg_node(std::shared_ptr operand) +{ + return std::make_shared(::odata::core::unary_operator_kind::Negate, operand); +} + +std::shared_ptr odata_query_node::create_parameter_alias_node(const ::odata::utility::string_t &alias) +{ + return std::make_shared(alias); +} + +std::shared_ptr odata_query_node::create_property_access_node( + const ::odata::utility::string_t &property_name, + std::shared_ptr parent) +{ + return std::make_shared(property_name, parent); +} + +std::shared_ptr odata_query_node::create_type_cast_node( + const ::odata::utility::string_t &type_name, + std::shared_ptr parent) +{ + return std::make_shared(type_name, parent); +} + +std::shared_ptr odata_query_node::create_lambda_node( + bool is_any, + std::shared_ptr expression, + const ::odata::utility::string_t ¶meter, + std::shared_ptr parent) +{ + return std::make_shared(is_any, expression, parameter, parent); +} + +std::shared_ptr odata_query_node::create_range_variable_node(const ::odata::utility::string_t &name) +{ + return std::make_shared(name); +} + +std::shared_ptr odata_query_node::create_function_call_node( + const ::odata::utility::string_t &name, + std::vector>> parameters) +{ + return std::make_shared(name, std::move(parameters)); +} + +}} \ No newline at end of file diff --git a/src/core/odata_query_node_visitor.cpp b/src/core/odata_query_node_visitor.cpp new file mode 100644 index 0000000..c491223 --- /dev/null +++ b/src/core/odata_query_node_visitor.cpp @@ -0,0 +1,14 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_query_node_visitor.h" + +namespace odata { namespace core +{ + + + +}} \ No newline at end of file diff --git a/src/core/odata_structured_value.cpp b/src/core/odata_structured_value.cpp new file mode 100644 index 0000000..b8d15c4 --- /dev/null +++ b/src/core/odata_structured_value.cpp @@ -0,0 +1,65 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_structured_value.h" +#include "odata/core/odata_collection_value.h" +#include "odata/core/odata_complex_value.h" +#include "odata/core/odata_enum_value.h" +#include "odata/core/odata_entity_value.h" +#include "odata/edm/edm_model_utility.h" + +using namespace odata::utility; +using namespace odata::edm; + +namespace odata { namespace core +{ + +void odata_structured_value::set_value(const ::odata::utility::string_t& property_name, const ::odata::utility::string_t& string_value) +{ + bool setted = false; + std::shared_ptr property_value; + if (get_property_value(property_name, property_value)) + { + std::shared_ptr property_type = property_value->get_value_type(); + if (property_type && (property_type->get_type_kind() == edm_type_kind_t::Primitive)) + { + setted = true; + m_properties[property_name] = std::make_shared(std::dynamic_pointer_cast(property_type), string_value); + } + } + + if (!setted) + { + m_properties[property_name] = std::make_shared(edm_primitive_type::STRING(), string_value); + } +} + +void odata_structured_value::set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value) +{ + m_properties[property_name] = property_value; +} + +void odata_structured_value::set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value) +{ + m_properties[property_name] = property_value; +} + +void odata_structured_value::set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value) +{ + m_properties[property_name] = property_value; +} + +void odata_structured_value::set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value) +{ + m_properties[property_name] = property_value; +} + +void odata_structured_value::set_value(const ::odata::utility::string_t& property_name, std::shared_ptr property_value) +{ + m_properties[property_name] = property_value; +} + +}} \ No newline at end of file diff --git a/src/core/odata_uri.cpp b/src/core/odata_uri.cpp new file mode 100644 index 0000000..8634e13 --- /dev/null +++ b/src/core/odata_uri.cpp @@ -0,0 +1,33 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_uri.h" + +namespace odata { namespace core +{ + +std::shared_ptr odata_uri::create_uri( + std::shared_ptr<::odata::core::odata_path> path, + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause, + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause, + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause, + std::shared_ptr<::odata::core::odata_search_clause> search_clause, + ::odata::common::nullable top, + ::odata::common::nullable skip, + ::odata::common::nullable count) +{ + return std::shared_ptr(new odata_uri( + path, + select_expand_clause, + filter_clause, + orderby_clause, + search_clause, + top, + skip, + count)); +} + +}} \ No newline at end of file diff --git a/src/core/odata_uri_parser.cpp b/src/core/odata_uri_parser.cpp new file mode 100644 index 0000000..e703136 --- /dev/null +++ b/src/core/odata_uri_parser.cpp @@ -0,0 +1,2493 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/core/odata_uri_parser.h" + +#define ERROR_MESSAGE_NOT_RELATIVE_URI() (U("not relative uri")) +#define ERROR_MESSAGE_UNSUPPORTED_SEGMENT(IDENTIFIER) ((IDENTIFIER) + U(" unsupported")) +#define ERROR_MESSAGE_RESOURCE_NOT_FOUND(IDENTIFIER) (U("resource ") + (IDENTIFIER) + U(" not found")) +#define ERROR_MESSAGE_UNEXPECTED_PATH_ROOT(IDENTIFIER) (U("unexpected ") + (IDENTIFIER) + U(" on path root")) +#define ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(IDENTIFIER) ((IDENTIFIER) + U(" cannot have parenthesis expression")) +#define ERROR_MESSAGE_EMPTY_SEGMENT_IDENTIFIER() (U("empty segment identifier")) +#define ERROR_MESSAGE_PARENTHESIS_MISMATCH() (U("parenthesis mismatch")) +#define ERROR_MESSAGE_NAME_MUST_BE_PROVIDED() (U("name must be provided for each value")) +#define ERROR_MESSAGE_NAME_MUST_NOT_DUPLICATE(NAME) (U("name ") + (NAME) + U(" must not duplicate")) +#define ERROR_MESSAGE_KEY_COUNT_MISMATCH() (U("key count mismatch")) +#define ERROR_MESSAGE_KEY_NOT_FOUND(NAME) (U("key name ") + (NAME) + U(" not found")) +#define ERROR_MESSAGE_CLOSE_CHAR_EXPECTED(CHAR) (::odata::utility::string_t(U("close char ")) + (CHAR) + U(" expected")) +#define ERROR_MESSAGE_NOT_OPEN_TYPE(NAME) (U("dynamic property ") +(NAME) + U(" under nonopen type")) +#define ERROR_MESSAGE_NO_NAVIGATION_SOURCE() (U("no navigation source for navigation property")) +#define ERROR_MESSAGE_PARAMETER_COUNT_MISMATCH() (U("parameter count mismatch")) +#define ERROR_MESSAGE_PARAMETER_NOT_FOUND(NAME) (U("parameter name ") + (NAME) + U(" not found")) +#define ERROR_MESSAGE_BOUND_OPERATION_IMPORT_FOUND(NAME) (U("bound operation import ") + (NAME) + U(" found")) +#define ERROR_MESSAGE_UNBOUND_OPERATION_FOUND(NAME) (U("unbound operation ") + (NAME) + U(" found")) +#define ERROR_MESSAGE_BINDING_PARAMETER_NOT_FOUND(NAME) (U("binding parameter for ") + (NAME) + U(" not found")) +#define ERROR_MESSAGE_BINDING_TYPE_MISMATCH(NAME) (U("binding type for ") + (NAME) + U(" mismatch")) +#define ERROR_MESSAGE_SEGMENT_FOLLOW(NAME) (::odata::utility::string_t(U("segment follow ")) + (NAME)) +#define ERROR_MESSAGE_SEGMENT_FOLLOW_NONCOMPOSABLE(NAME) (U("segment follow noncomposable operation (import) ") + (NAME)) +#define ERROR_MESSAGE_INVALID_COUNT_QUERY(QUERY) (U("invalid count query ") + (QUERY)) +#define ERROR_MESSAGE_INVALID_CHARACTER(CHAR) (::odata::utility::string_t(U("invalid character ")) + (CHAR)) +#define ERROR_MESSAGE_UNEXPECTED_TOKEN(NAME) (U("unexpected token ") + (NAME)) +#define ERROR_MESSAGE_TOKEN_KIND_EXPECTED(KIND) (::odata::utility::string_t(U("token kind expected ")) + (KIND)) +#define ERROR_MESSAGE_CHAR_KIND_EXPECTED(KIND) (::odata::utility::string_t(U("char kind expected ")) + (KIND)) +#define ERROR_MESSAGE_INVALID_VALUE(VALUE) (U("invalid value ") + VALUE) +#define ERROR_MESSAGE_EMPTY_NAME_VALUE_PAIR() (U("empty name-value pair")) +#define ERROR_MESSAGE_DUPLICATE_RANGE_VARIABLE(NAME) (U("duplicate range variable ") + (NAME)) +#define ERROR_MESSAGE_FILTER_ON_NONCOLLECTION() (U("$filter on non-collection resource")) +#define ERROR_MESSAGE_ORDERBY_ON_NONCOLLECTION() (U("$orderby on non-collection resource")) +#define ERROR_MESSAGE_SELECT_OR_EXPAND_ON_NONSTRUCTURED_TYPE() (U("$select or $expand on non-structured type")) + +static const ::odata::utility::string_t SegmentIdentifierMetadata = U("$metadata"); +static const ::odata::utility::string_t SegmentIdentifierBatch = U("$batch"); +static const ::odata::utility::string_t SegmentIdentifierCount = U("$count"); +static const ::odata::utility::string_t SegmentIdentifierValue = U("$value"); +static const ::odata::utility::string_t SegmentIdentifierRef = U("$ref"); +static const ::odata::utility::string_t SegmentIdentifierAll = U("$all"); +static const ::odata::utility::string_t SegmentIdentifierEntity = U("$entity"); +static const ::odata::utility::string_t SegmentIdentifierCrossJoin = U("$crossjoin"); + +static const ::odata::utility::string_t QueryOptionSelect = U("$select"); +static const ::odata::utility::string_t QueryOptionExpand = U("$expand"); +static const ::odata::utility::string_t QueryOptionFilter = U("$filter"); +static const ::odata::utility::string_t QueryOptionOrderBy = U("$orderby"); +static const ::odata::utility::string_t QueryOptionSearch = U("$search"); +static const ::odata::utility::string_t QueryOptionTop = U("$top"); +static const ::odata::utility::string_t QueryOptionSkip = U("$skip"); +static const ::odata::utility::string_t QueryOptionCount = U("$count"); + +static const ::odata::utility::string_t KeywordTrue = U("true"); +static const ::odata::utility::string_t KeywordFalse = U("false"); +static const ::odata::utility::string_t KeywordNull = U("null"); +static const ::odata::utility::string_t KeywordOr = U("or"); +static const ::odata::utility::string_t KeywordAnd = U("and"); +static const ::odata::utility::string_t KeywordEqual = U("eq"); +static const ::odata::utility::string_t KeywordNotEqual = U("ne"); +static const ::odata::utility::string_t KeywordGreaterThan = U("gt"); +static const ::odata::utility::string_t KeywordGreaterThanOrEqual = U("ge"); +static const ::odata::utility::string_t KeywordLessThan = U("lt"); +static const ::odata::utility::string_t KeywordLessThanOrEqual = U("le"); +static const ::odata::utility::string_t KeywordHas = U("has"); +static const ::odata::utility::string_t KeywordAdd = U("add"); +static const ::odata::utility::string_t KeywordSub = U("sub"); +static const ::odata::utility::string_t KeywordMultiply = U("mul"); +static const ::odata::utility::string_t KeywordDivide = U("div"); +static const ::odata::utility::string_t KeywordModulo = U("mod"); +static const ::odata::utility::string_t KeywordNot = U("not"); +static const ::odata::utility::string_t KeywordAll = U("all"); +static const ::odata::utility::string_t KeywordAny = U("any"); +static const ::odata::utility::string_t KeywordAsc = U("asc"); +static const ::odata::utility::string_t KeywordDesc = U("desc"); + +static const ::odata::utility::string_t TokenNone = U(""); +static const ::odata::utility::string_t TokenLeftParen = U("("); +static const ::odata::utility::string_t TokenRightParen = U(")"); +static const ::odata::utility::string_t TokenComma = U(","); +static const ::odata::utility::string_t TokenEqual = U("="); +static const ::odata::utility::string_t TokenSlash = U("/"); +static const ::odata::utility::string_t TokenStar = U("*"); +static const ::odata::utility::string_t TokenQuestion = U("?"); +static const ::odata::utility::string_t TokenMinus = U("-"); +static const ::odata::utility::string_t TokenDot = U("."); +static const ::odata::utility::string_t TokenColon = U(":"); + +static const ::odata::utility::string_t TokenKindIdentifier = U("Identifier"); +static const ::odata::utility::string_t TokenKindLiteral = U("Literal"); +static const ::odata::utility::string_t TokenKindParameterAlias = U("ParameterAlias"); + +static const ::odata::utility::string_t CharKindDigit = U("Digit"); +static const ::odata::utility::string_t CharKindHexDigit = U("HexDigit"); + +static const ::odata::utility::string_t DoubleLiteralInf = U("INF"); +static const ::odata::utility::string_t DoubleLiteralNegativeInf = U("-INF"); +static const ::odata::utility::string_t DoubleLiteralNan = U("NaN"); +static const ::odata::utility::string_t LiteralIt = U("$it"); + +static const ::odata::utility::string_t LiteralPrefixDuration = U("duration"); +static const ::odata::utility::string_t LiteralPrefixBinary = U("binary"); +static const ::odata::utility::string_t LiteralPrefixGeography = U("geography"); +static const ::odata::utility::string_t LiteralPrefixGeometry = U("geometry"); + +namespace odata { namespace core +{ + +// --BEGIN-- odata_path_parser + +void odata_path_parser::validate_is_nonempty_relative_uri(const ::odata::utility::string_t& uri) +{ + if (uri.empty() || uri[0] != '/') + { + throw odata_exception(ERROR_MESSAGE_NOT_RELATIVE_URI()); + } +} + +std::shared_ptr<::odata::core::odata_path> odata_path_parser::parse_path(const ::odata::utility::string_t& path) +{ + validate_is_nonempty_relative_uri(path); + + auto raw_segments = ::odata::utility::uri::split_path(path); + + bind_path(raw_segments); + + auto odata_path = std::make_shared<::odata::core::odata_path>(m_bound_segments); + + return odata_path; +} + +void odata_path_parser::validate_can_bind_next_segment() +{ + auto segment = previous_segment(); + + switch (segment->segment_type()) + { + case ::odata::core::odata_path_segment_type::Metadata: + throw odata_exception(ERROR_MESSAGE_SEGMENT_FOLLOW(SegmentIdentifierMetadata)); + + case ::odata::core::odata_path_segment_type::Batch: + throw odata_exception(ERROR_MESSAGE_SEGMENT_FOLLOW(SegmentIdentifierBatch)); + + case ::odata::core::odata_path_segment_type::Value: + throw odata_exception(ERROR_MESSAGE_SEGMENT_FOLLOW(SegmentIdentifierValue)); + + case ::odata::core::odata_path_segment_type::Count: + throw odata_exception(ERROR_MESSAGE_SEGMENT_FOLLOW(SegmentIdentifierCount)); + + case ::odata::core::odata_path_segment_type::Ref: + throw odata_exception(ERROR_MESSAGE_SEGMENT_FOLLOW(SegmentIdentifierRef)); + + case ::odata::core::odata_path_segment_type::Operation: + { + auto operation = segment->as()->operation(); + if (!operation->is_composable()) + { + throw odata_exception(ERROR_MESSAGE_SEGMENT_FOLLOW_NONCOMPOSABLE(operation->get_name())); + } + } + break; + + case ::odata::core::odata_path_segment_type::OperationImport: + { + auto operation_import = segment->as()->operation_import(); + if (!operation_import->get_operation_type()->is_composable()) + { + throw odata_exception(ERROR_MESSAGE_SEGMENT_FOLLOW_NONCOMPOSABLE(operation_import->get_name())); + } + } + break; + } +} + +void odata_path_parser::bind_path(std::vector<::odata::utility::string_t> raw_segments) +{ + m_bound_segments.clear(); + + bool should_be_no_segment = false; + + for (::size_t i = 0; i < raw_segments.size(); ++i) + { + if (i == 0) + { + bind_first_segment(raw_segments[0]); + } + else + { + validate_can_bind_next_segment(); + bind_next_segment(raw_segments[i]); + } + + compute_target_navigation_source_and_type(); + } +} + +void odata_path_parser::extract_segment_identifier_and_parenthesis_exp(const ::odata::utility::string_t& segment_str, ::odata::utility::string_t& identifier, ::odata::utility::string_t& parenthesis_exp) +{ + auto parenthesis_start = segment_str.find('('); + if (parenthesis_start == ::odata::utility::string_t::npos) + { + // If the segment string does not contain '(', identifier is the string itself and parenthesis expression is empty. + identifier = segment_str; + parenthesis_exp.clear(); + } + else + { + // At least we have one char '(' in the string. + auto parenthesis_end = segment_str.size() - 1; + if (segment_str[parenthesis_end] == ')') + { + if (parenthesis_start < 1) + { + throw odata_exception(ERROR_MESSAGE_EMPTY_SEGMENT_IDENTIFIER()); + } + + identifier = segment_str.substr(0, parenthesis_start); + parenthesis_exp = segment_str.substr(parenthesis_start + 1, parenthesis_end - parenthesis_start - 1); + } + else + { + throw odata_exception(ERROR_MESSAGE_PARENTHESIS_MISMATCH()); + } + } +} + +void odata_path_parser::bind_first_segment(const ::odata::utility::string_t &segment_str) +{ + ::odata::utility::string_t identifier; + ::odata::utility::string_t parenthesis_exp; + extract_segment_identifier_and_parenthesis_exp(segment_str, identifier, parenthesis_exp); + + if (identifier == SegmentIdentifierMetadata) + { + if (!parenthesis_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + m_bound_segments.push_back(odata_path_segment::create_metadata_segment()); + + return; + } + else if (identifier == SegmentIdentifierBatch) + { + if (!parenthesis_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + m_bound_segments.push_back(odata_path_segment::create_batch_segment()); + + return; + } + else if (identifier == SegmentIdentifierCount || identifier == SegmentIdentifierValue || identifier == SegmentIdentifierRef) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PATH_ROOT(identifier)); + } + else if (identifier == SegmentIdentifierAll || identifier == SegmentIdentifierEntity || identifier == SegmentIdentifierCrossJoin) + { + throw odata_exception(ERROR_MESSAGE_UNSUPPORTED_SEGMENT(identifier)); + } + else if (try_bind_as_navigation_source(identifier, parenthesis_exp)) + { + return; + } + else if (try_bind_as_operation_import(identifier, parenthesis_exp)) + { + return; + } + + throw odata_exception(ERROR_MESSAGE_RESOURCE_NOT_FOUND(identifier)); +} + +void odata_path_parser::bind_next_segment(const ::odata::utility::string_t &segment_str) +{ + ::odata::utility::string_t identifier; + ::odata::utility::string_t parenthesis_exp; + extract_segment_identifier_and_parenthesis_exp(segment_str, identifier, parenthesis_exp); + + if (try_bind_as_value(identifier, parenthesis_exp)) + { + return; + } + + if (try_bind_as_count(identifier, parenthesis_exp)) + { + return; + } + + if (try_bind_as_ref(identifier, parenthesis_exp)) + { + return; + } + + if (try_bind_as_declared_property(identifier, parenthesis_exp)) + { + return; + } + + if (try_bind_as_type(identifier, parenthesis_exp)) + { + return; + } + + if (try_bind_as_operation(identifier, parenthesis_exp)) + { + return; + } + + bind_as_dynamic_property(identifier, parenthesis_exp); +} + +bool odata_path_parser::try_bind_as_navigation_source(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &key_exp) +{ + auto container = model()->find_container(); + if (container == nullptr) + { + return false; + } + + auto entity_set = container->find_entity_set(identifier); + if (entity_set != nullptr) + { + auto entity_set_segment = odata_path_segment::create_entity_set_segment(entity_set); + m_bound_segments.push_back(entity_set_segment); + + bind_as_key(key_exp); + + return true; + } + else + { + auto singleton = container->find_singleton(identifier); + if (singleton != nullptr) + { + auto singleton_segment = odata_path_segment::create_singleton_segment(singleton); + m_bound_segments.push_back(singleton_segment); + + if (!key_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + return true; + } + } + + return false; +} + +std::vector>> odata_path_parser::parse_named_values(const ::odata::utility::string_t &exp) +{ + std::vector>> result; + + // Indicates if there is a pair has its name missing. + bool has_name_missing = false; + ::size_t num_pairs_parsed = 0; + + auto lexer = odata_expression_lexer::create_lexer(exp); + while (lexer->current_token()->token_kind() != odata_expression_token_kind::End) + { + ::odata::utility::string_t name; + std::shared_ptr<::odata::core::odata_primitive_value> value; + + if (lexer->current_token()->token_kind() == odata_expression_token_kind::Identifier) + { + name = lexer->current_token()->text(); + + lexer->next_token(); + lexer->eat_token(odata_expression_token_kind::Equal, TokenEqual); + + value = lexer->current_token()->to_primitive_value(); + } + else if (lexer->current_token()->is_literal()) + { + has_name_missing = true; + + value = lexer->current_token()->to_primitive_value(); + } + else + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_TOKEN(lexer->current_token()->text())); + } + + if (value == nullptr) + { + throw odata_exception(ERROR_MESSAGE_INVALID_VALUE(lexer->current_token()->text())); + } + + auto pair = std::make_pair(name, value); + + ++num_pairs_parsed; + + auto existing = std::find_if(result.begin(), result.end(), [&pair](std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_primitive_value>> p) + { + return pair.first == p.first; + }); + + if (!pair.first.empty() && existing != result.end()) + { + throw odata_exception(ERROR_MESSAGE_NAME_MUST_NOT_DUPLICATE(pair.first)); + } + + result.push_back(pair); + + lexer->next_token(); + if (lexer->current_token()->token_kind() == odata_expression_token_kind::Comma) + { + lexer->next_token(); + + if (lexer->current_token()->token_kind() == odata_expression_token_kind::End) + { + throw odata_exception(ERROR_MESSAGE_EMPTY_NAME_VALUE_PAIR()); + } + } + } + + if (has_name_missing && num_pairs_parsed > 1) + { + // Multiple keys must have name provided for each one. + throw odata_exception(ERROR_MESSAGE_NAME_MUST_BE_PROVIDED()); + } + + return result; +} + +std::shared_ptr<::odata::edm::edm_named_type> odata_path_parser::get_target_type() +{ + auto segment = previous_segment(); + + switch (segment->segment_type()) + { + case ::odata::core::odata_path_segment_type::EntitySet: + return std::make_shared<::odata::edm::edm_collection_type>(segment->as()->entity_type()); + + case ::odata::core::odata_path_segment_type::Singleton: + return segment->as()->entity_type(); + + case ::odata::core::odata_path_segment_type::StructuralProperty: + return segment->as()->property()->get_property_type(); + + case ::odata::core::odata_path_segment_type::NavigationProperty: + return segment->as()->navigation_type()->get_navigation_type(); + + case ::odata::core::odata_path_segment_type::Operation: + return segment->as()->operation()->get_operation_return_type(); + + case ::odata::core::odata_path_segment_type::OperationImport: + return segment->as()->operation_import()->get_operation_type()->get_operation_return_type(); + + case ::odata::core::odata_path_segment_type::Type: + return segment->as()->type(); + + case ::odata::core::odata_path_segment_type::Key: + return segment->as()->target_entity_type(); + + default: + // Cannot determine the target type. + return ::odata::edm::edm_named_type::EDM_UNKNOWN(); + } +} + +std::shared_ptr<::odata::edm::edm_navigation_source> odata_path_parser::get_target_navigation_source() +{ + auto segment = previous_segment(); + + switch (segment->segment_type()) + { + case ::odata::core::odata_path_segment_type::EntitySet: + return segment->as()->entity_set(); + + case ::odata::core::odata_path_segment_type::Singleton: + return segment->as()->singleton(); + + case ::odata::core::odata_path_segment_type::NavigationProperty: + return segment->as()->navigation_type()->get_binded_navigation_source(); + + case ::odata::core::odata_path_segment_type::Key: + return segment->as()->navigation_source(); + + default: + // Cannot determine the target navigation source. + return nullptr; + } +} + +void odata_path_parser::bind_as_key(const ::odata::utility::string_t &key_exp) +{ + if (key_exp.empty()) + { + // Skip binding if key expression is empty. + return; + } + + compute_target_navigation_source_and_type(); + auto target_entity_type = ::odata::edm::edm_model_utility::get_entity_type(target_type()); + + auto keys = parse_named_values(key_exp); + auto keys_from_model = target_entity_type->get_key_with_parents(); + if (keys_from_model.size() != keys.size()) + { + throw odata_exception(ERROR_MESSAGE_KEY_COUNT_MISMATCH()); + } + + for (auto it = keys_from_model.begin(); it != keys_from_model.end(); ++it) + { + auto existing = std::find_if(keys.begin(), keys.end(), [it](std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_primitive_value>> p) + { + return *it == p.first; + }); + + // Name can be omitted when there is only one key. + if (existing == keys.end() && !(keys.size() == 1 && keys[0].first.empty())) + { + throw odata_exception(ERROR_MESSAGE_KEY_NOT_FOUND(*it)); + } + } + + if (keys[0].first.empty()) + { + // Even if the name can be omitted when only one key but we need to fill the name for service to use. + keys[0] = std::make_pair(keys_from_model[0], keys[0].second); + } + + m_bound_segments.push_back(odata_path_segment::create_key_segment(target_navigation_source(), target_entity_type, std::move(keys))); +} + +bool odata_path_parser::try_bind_as_value(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp) +{ + if (identifier != SegmentIdentifierValue) + { + return false; + } + + if (!empty_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + m_bound_segments.push_back(odata_path_segment::create_value_segment()); + + return true; +} + +bool odata_path_parser::try_bind_as_count(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp) +{ + if (identifier != SegmentIdentifierCount) + { + return false; + } + + if (!empty_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + m_bound_segments.push_back(odata_path_segment::create_count_segment()); + + return true; +} + +bool odata_path_parser::try_bind_as_ref(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp) +{ + if (identifier != SegmentIdentifierRef) + { + return false; + } + + if (!empty_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + m_bound_segments.push_back(odata_path_segment::create_ref_segment()); + + return true; +} + +bool odata_path_parser::try_bind_as_declared_property(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &key_exp) +{ + auto owning_type = ::odata::edm::edm_model_utility::get_structured_type(target_type()); + if (owning_type == nullptr) + { + // Not structured type. + return false; + } + + auto property = owning_type->find_property(identifier); + if (property == nullptr) + { + // Property not declared. + return false; + } + + if (property->get_property_type()->get_type_kind() == ::odata::edm::edm_type_kind_t::Navigation) + { + // Navigation property should have binded navigation source. + auto navigation_type = std::static_pointer_cast<::odata::edm::edm_navigation_type>(property->get_property_type()); + if (navigation_type->get_binded_navigation_source() == nullptr && !navigation_type->is_contained()) + { + throw odata_exception(ERROR_MESSAGE_NO_NAVIGATION_SOURCE()); + } + + // Single navigation property cannot have key expression. + if (!::odata::edm::edm_model_utility::is_collection_of_entity(navigation_type->get_navigation_type()) && !key_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + m_bound_segments.push_back(odata_path_segment::create_navigation_property_segment(owning_type, property, navigation_type)); + + bind_as_key(key_exp); + } + else + { + // Structural property. + m_bound_segments.push_back(odata_path_segment::create_structural_property_segment(owning_type, property)); + + // Structural property cannot have key expression. + if (!key_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + } + + return true; +} + +bool odata_path_parser::try_bind_as_type(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp) +{ + std::shared_ptr<::odata::edm::edm_named_type> type = model()->find_entity_type(identifier); + if (type == nullptr) + { + type = model()->find_complex_type(identifier); + + if (type == nullptr) + { + // Not a type segment. + return false; + } + } + + if (!empty_exp.empty()) + { + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + + m_bound_segments.push_back(odata_path_segment::create_type_segment(type)); + + return true; +} + +void odata_path_parser::validate_parameters_match( + const std::vector>> ¶meters_parsed, + const std::vector> ¶meters_from_model, + bool has_binding_parameter) +{ + ::size_t skip = has_binding_parameter ? 1 : 0; + + if (parameters_from_model.size() != parameters_parsed.size() + skip) + { + throw odata_exception(ERROR_MESSAGE_PARAMETER_COUNT_MISMATCH()); + } + + for (::size_t i = skip; i < parameters_from_model.size(); ++i) + { + auto param_name = parameters_from_model[i]->get_param_name(); + + auto existing = std::find_if(parameters_parsed.begin(), parameters_parsed.end(), [¶m_name](std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_primitive_value>> p) + { + return param_name == p.first; + }); + + if (existing == parameters_parsed.end()) + { + throw odata_exception(ERROR_MESSAGE_PARAMETER_NOT_FOUND(param_name)); + } + } +} + +bool odata_path_parser::try_bind_as_operation_import(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t ¶meters_exp) +{ + auto container = model()->find_container(); + if (container == nullptr) + { + return false; + } + + auto operation_import = container->find_operation_import(identifier); + if (operation_import == nullptr) + { + // Operation import not found. + return false; + } + + auto operation = operation_import->get_operation_type(); + if (operation->is_bound()) + { + // Operation import used as path root cannot be bound. + throw odata_exception(ERROR_MESSAGE_BOUND_OPERATION_IMPORT_FOUND(identifier)); + } + + auto parameters = parse_named_values(parameters_exp); + auto parameters_from_model = operation->get_operation_parameters(); + validate_parameters_match(parameters, parameters_from_model); + + m_bound_segments.push_back(odata_path_segment::create_operation_import_segment(operation_import, std::move(parameters))); + + return true; +} + +bool odata_path_parser::try_bind_as_operation(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t ¶meters_exp) +{ + auto operation = model()->find_operation_type(identifier); + if (operation == nullptr) + { + // Operation not found. + return false; + } + + if (!operation->is_bound()) + { + // Operation used in path cannot be unbound. + throw odata_exception(ERROR_MESSAGE_UNBOUND_OPERATION_FOUND(identifier)); + } + + auto parameters = parse_named_values(parameters_exp); + auto parameters_from_model = operation->get_operation_parameters(); + + // Validate the binding parameter. + if (parameters_from_model.size() < 1) + { + throw odata_exception(ERROR_MESSAGE_BINDING_PARAMETER_NOT_FOUND(identifier)); + } + + auto binding_type = parameters_from_model[0]->get_param_type(); + auto previous_type = target_type(); + if (previous_type == nullptr || binding_type->get_full_name() != previous_type->get_full_name()) + { + // Should consider derived type comparison. + throw odata_exception(ERROR_MESSAGE_BINDING_TYPE_MISMATCH(identifier)); + } + + // Validate the remaining parameters. + validate_parameters_match(parameters, parameters_from_model, /*has_binding_parameter*/true); + + m_bound_segments.push_back(odata_path_segment::create_operation_segment(operation, std::move(parameters))); + + return true; +} + +void odata_path_parser::bind_as_dynamic_property(const ::odata::utility::string_t &identifier, const ::odata::utility::string_t &empty_exp) +{ + if (previous_segment()->segment_type() != ::odata::core::odata_path_segment_type::DynamicProperty) + { + auto target_structured_type = ::odata::edm::edm_model_utility::get_structured_type(target_type()); + if (target_structured_type == nullptr || !target_structured_type->is_open_type()) + { + throw odata_exception(ERROR_MESSAGE_NOT_OPEN_TYPE(identifier)); + } + + if (!empty_exp.empty()) + { + // Dynamic navigation property not supported on open type. + throw odata_exception(ERROR_MESSAGE_UNEXPECTED_PARENTHESIS_EXPRESSION(identifier)); + } + } + + m_bound_segments.push_back(odata_path_segment::create_dynamic_property_segment(identifier)); +} + +void odata_path_parser::compute_target_navigation_source_and_type() +{ + auto new_navigation_source = get_target_navigation_source(); + if (new_navigation_source != nullptr) + { + m_target_navigation_source = new_navigation_source; + } + + auto new_type = get_target_type(); + if (new_type != nullptr) + { + m_target_type = new_type; + } +} + +// --END-- odata_path_parser + +// --BEGIN-- odata_select_item, odata_expand_item + +std::shared_ptr<::odata::core::odata_select_item> odata_select_item::create(std::shared_ptr<::odata::core::odata_query_node> end) +{ + return std::make_shared<::odata::core::odata_select_item>(end); +} + +std::shared_ptr<::odata::core::odata_expand_item> odata_expand_item::create(std::shared_ptr<::odata::core::odata_query_node> end) +{ + return create( + end, + nullptr, + nullptr, + nullptr, + nullptr, + null_value, + null_value, + null_value); +} + +std::shared_ptr<::odata::core::odata_expand_item> odata_expand_item::create( + std::shared_ptr<::odata::core::odata_query_node> end, + std::shared_ptr<::odata::core::odata_select_expand_clause> select_expand_clause, + std::shared_ptr<::odata::core::odata_filter_clause> filter_clause, + std::shared_ptr<::odata::core::odata_orderby_clause> orderby_clause, + std::shared_ptr<::odata::core::odata_search_clause> search_clause, + ::odata::common::nullable top, + ::odata::common::nullable skip, + ::odata::common::nullable count) +{ + return std::shared_ptr<::odata::core::odata_expand_item>( + new odata_expand_item( + end, + select_expand_clause, + filter_clause, + orderby_clause, + search_clause, + top, + skip, + count)); +} + +// --END-- odata_select_item, odata_expand_item + +// --BEGIN-- odata_query_option_parser + +std::shared_ptr<::odata::core::odata_select_expand_clause> odata_query_option_parser::parse_select_and_expand(const ::odata::utility::string_t& select_query, const ::odata::utility::string_t& expand_query) +{ + if (select_query.empty() && expand_query.empty()) + { + return null_value; + } + + // Should be EntityType, ComplexType, Collection(EntityType) or Collection(ComplexType). + if (::odata::edm::edm_model_utility::get_structured_type(target_type()) == nullptr) + { + throw odata_exception(ERROR_MESSAGE_SELECT_OR_EXPAND_ON_NONSTRUCTURED_TYPE()); + } + + auto select_items = parse_select(select_query); + auto expand_items = parse_expand(expand_query); + + auto select_expand_clause = std::make_shared<::odata::core::odata_select_expand_clause>( + std::move(select_items), + std::move(expand_items)); + return select_expand_clause; +} + +std::shared_ptr<::odata::core::odata_filter_clause> odata_query_option_parser::parse_filter( + const ::odata::utility::string_t& filter_query) +{ + if (filter_query.empty()) + { + return null_value; + } + + if (target_type()->get_type_kind() != ::odata::edm::edm_type_kind_t::Collection) + { + throw odata_exception(ERROR_MESSAGE_FILTER_ON_NONCOLLECTION()); + } + + auto expression = odata_expression_parser::parse_expression(filter_query, odata_expression_parser_subject::FilterClause); + auto range_variable = create_implicit_range_variable(); + + auto filter_clause = std::make_shared(expression, range_variable); + return filter_clause; +} + +std::shared_ptr<::odata::core::odata_orderby_clause> odata_query_option_parser::parse_orderby(const ::odata::utility::string_t& orderby_query) +{ + if (orderby_query.empty()) + { + return null_value; + } + + if (target_type()->get_type_kind() != ::odata::edm::edm_type_kind_t::Collection) + { + throw odata_exception(ERROR_MESSAGE_ORDERBY_ON_NONCOLLECTION()); + } + + auto lexer = odata_expression_lexer::create_lexer(orderby_query); + + std::vector, bool>> items; + + while (lexer->current_token()->token_kind() != odata_expression_token_kind::End) + { + auto expression = odata_expression_parser::parse_expression(lexer, odata_expression_parser_subject::OrderByClause); + + bool ascending = true; + + if (lexer->current_token()->is_identifier(KeywordDesc)) + { + lexer->next_token(); + + ascending = false; + } + + items.push_back(std::make_pair(expression, ascending)); + + if (lexer->current_token()->token_kind() != odata_expression_token_kind::Comma) + { + break; + } + + // Skip comma. + lexer->next_token(); + } + + auto range_variable = create_implicit_range_variable(); + + auto orderby_clause = std::make_shared<::odata::core::odata_orderby_clause>(std::move(items), range_variable); + return orderby_clause; +} + +std::shared_ptr<::odata::core::odata_search_clause> odata_query_option_parser::parse_search(const ::odata::utility::string_t& search_query) +{ + if (search_query.empty()) + { + return null_value; + } + + auto expression = odata_expression_parser::parse_expression(search_query, odata_expression_parser_subject::SearchClause); + + auto search_clause = std::make_shared<::odata::core::odata_search_clause>(expression); + return search_clause; +} + +::odata::common::nullable odata_query_option_parser::parse_top(const ::odata::utility::string_t& top_query) +{ + if (top_query.empty()) + { + return null_value; + } + + return ::odata::utility::conversions::scan_string(top_query); +} + +::odata::common::nullable odata_query_option_parser::parse_skip(const ::odata::utility::string_t& skip_query) +{ + if (skip_query.empty()) + { + return null_value; + } + + return ::odata::utility::conversions::scan_string(skip_query); +} + +::odata::common::nullable odata_query_option_parser::parse_count(const ::odata::utility::string_t& count_query) +{ + if (count_query.empty()) + { + return null_value; + } + else if (count_query == KeywordTrue) + { + return true; + } + else if (count_query == KeywordFalse) + { + return false; + } + else + { + throw odata_exception(ERROR_MESSAGE_INVALID_COUNT_QUERY(count_query)); + } +} + +std::shared_ptr odata_query_option_parser::create_implicit_range_variable() const +{ + auto _target_type = ::odata::edm::edm_model_utility::get_collection_element_type(target_type()); + if (_target_type->get_type_kind() == ::odata::edm::edm_type_kind_t::Entity) + { + return std::make_shared(LiteralIt, _target_type, target_navigation_source()); + } + else + { + return std::make_shared(LiteralIt, _target_type); + } +} + +std::vector> odata_query_option_parser::parse_select( + const ::odata::utility::string_t& select_query) +{ + auto lexer = odata_expression_lexer::create_lexer(select_query); + + std::vector> items; + + while (lexer->current_token()->token_kind() != odata_expression_token_kind::End) + { + items.push_back(odata_select_item::create( + odata_expression_parser::parse_expression(lexer, odata_expression_parser_subject::SelectClause))); + + if (lexer->current_token()->token_kind() != odata_expression_token_kind::Comma) + { + break; + } + + // Skip comma. + lexer->next_token(); + } + + return std::move(items); +} + +std::vector> odata_query_option_parser::parse_expand( + const ::odata::utility::string_t& expand_query) +{ + auto lexer = odata_expression_lexer::create_lexer(expand_query); + + std::vector> items; + + while (lexer->current_token()->token_kind() != odata_expression_token_kind::End) + { + auto expression = odata_expression_parser::parse_expression(lexer, odata_expression_parser_subject::ExpandClause); + + if (lexer->current_token()->token_kind() == odata_expression_token_kind::LeftParen) + { + auto expand_options_text = lexer->advance_through_balanced_parenthesis(); + auto query_options = odata_uri_parser::split_query_options(expand_options_text, true); + + ::odata::utility::string_t select_query; + ::odata::utility::string_t expand_query; + ::odata::utility::string_t filter_query; + ::odata::utility::string_t orderby_query; + ::odata::utility::string_t search_query; + ::odata::utility::string_t top_query; + ::odata::utility::string_t skip_query; + ::odata::utility::string_t count_query; + + for (auto iter = query_options.begin(); iter != query_options.end(); ++iter) + { + auto &option = *iter; + + if (option.first == QueryOptionSelect) + { + select_query = option.second; + } + else if (option.first == QueryOptionExpand) + { + expand_query = option.second; + } + else if (option.first == QueryOptionFilter) + { + filter_query = option.second; + } + else if (option.first == QueryOptionOrderBy) + { + orderby_query = option.second; + } + else if (option.first == QueryOptionSearch) + { + search_query = option.second; + } + else if (option.first == QueryOptionTop) + { + top_query = option.second; + } + else if (option.first == QueryOptionSkip) + { + skip_query = option.second; + } + else if (option.first == QueryOptionCount) + { + count_query = option.second; + } + } + + items.push_back(odata_expand_item::create( + expression, + parse_select_and_expand(select_query, expand_query), + parse_filter(filter_query), + parse_orderby(orderby_query), + parse_search(search_query), + parse_top(top_query), + parse_skip(skip_query), + parse_count(count_query))); + } + else + { + items.push_back(odata_expand_item::create(expression)); + } + + if (lexer->current_token()->token_kind() != odata_expression_token_kind::Comma) + { + break; + } + + // Skip comma. + lexer->next_token(); + } + + return std::move(items); +} + +// --END-- odata_query_option_parser + +// --BEGIN-- odata_expression_lexer + +std::shared_ptr odata_expression_token::create_token(int token_kind, const ::odata::utility::string_t &text, ::size_t pos) +{ + return std::make_shared(token_kind, text, pos); +} + +bool odata_expression_token::is_literal() const +{ + switch (m_token_kind) + { + case odata_expression_token_kind::BinaryLiteral: + case odata_expression_token_kind::BooleanLiteral: + case odata_expression_token_kind::DateTimeOffsetLiteral: + case odata_expression_token_kind::DecimalLiteral: + case odata_expression_token_kind::DoubleLiteral: + case odata_expression_token_kind::DurationLiteral: + case odata_expression_token_kind::GuidLiteral: + case odata_expression_token_kind::Int64Literal: + case odata_expression_token_kind::Int32Literal: + case odata_expression_token_kind::NullLiteral: + case odata_expression_token_kind::QuotedLiteral: + case odata_expression_token_kind::SingleLiteral: + case odata_expression_token_kind::StringLiteral: + return true; + + default: + return false; + } +} + +bool odata_expression_token::is_identifier() const +{ + return m_token_kind == odata_expression_token_kind::Identifier; +} + +bool odata_expression_token::is_identifier(const ::odata::utility::string_t &text) const +{ + return is_identifier() && m_text == text; +} + +std::shared_ptr<::odata::core::odata_primitive_value> odata_expression_token::to_primitive_value() const +{ + switch (m_token_kind) + { + case odata_expression_token_kind::BooleanLiteral: + return std::make_shared(::odata::edm::edm_primitive_type::BOOLEAN(), m_text); + + case odata_expression_token_kind::DoubleLiteral: + return std::make_shared(::odata::edm::edm_primitive_type::DOUBLE(), m_text); + + case odata_expression_token_kind::Int32Literal: + return std::make_shared(::odata::edm::edm_primitive_type::INT32(), m_text); + + case odata_expression_token_kind::StringLiteral: + if (m_text.size() > 1) + { + auto string_rep = m_text.substr(1, m_text.size() - 2); + return std::make_shared(::odata::edm::edm_primitive_type::STRING(), string_rep); + } + else + { + return std::make_shared(::odata::edm::edm_primitive_type::STRING(), m_text); + } + + default: + return nullptr; + } +} + +std::shared_ptr odata_expression_lexer::create_lexer(const ::odata::utility::string_t &expression) +{ + return std::make_shared(expression); +} + +void odata_expression_lexer::next_token() +{ + scan_whitespaces(); + + auto token_start = m_pos; + switch (current_char()) + { + case U('('): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::LeftParen, TokenLeftParen, token_start); + return; + + case U(')'): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::RightParen, TokenRightParen, token_start); + return; + + case U(','): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::Comma, TokenComma, token_start); + return; + + case U('='): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::Equal, TokenEqual, token_start); + return; + + case U('/'): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::Slash, TokenSlash, token_start); + return; + + case U('.'): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::Dot, TokenDot, token_start); + return; + + case U('*'): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::Star, TokenStar, token_start); + return; + + case U(':'): + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::Colon, TokenColon, token_start); + return; + + case U('-'): + if (::odata::utility::is_digit(peek_char())) + { + auto kind = scan_number(); + if (kind == odata_expression_token_kind::GuidLiteral) + { + // Guid literals cannot have sign so rewind. + m_pos = token_start; + } + else + { + m_token = odata_expression_token::create_token(kind, extract_text(token_start), token_start); + return; + } + } + else if (peek_char() == U('I') || peek_char() == U('N')) + { + // Advance to 'I' or 'N' + next_char(); + + scan_identifier(); + + auto text = extract_text(token_start); + + if (text == DoubleLiteralNegativeInf) + { + m_token = odata_expression_token::create_token(odata_expression_token_kind::DoubleLiteral, text, token_start); + return; + } + + m_pos = token_start; + } + + next_char(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::Minus, TokenMinus, token_start); + return; + + case U('\''): + scan_string(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::StringLiteral, extract_text(token_start), token_start); + return; + + case U('@'): + next_char(); + scan_identifier(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::ParameterAlias, extract_text(token_start), token_start); + return; + + case U('{'): + case U('['): + scan_json_object_or_array(); + m_token = odata_expression_token::create_token(odata_expression_token_kind::JsonObjectOrArray, extract_text(token_start), token_start); + return; + + case 0: + m_token = odata_expression_token::create_token(odata_expression_token_kind::End, TokenNone, m_expression.size()); + return; + } + + if (is_valid_start_of_identifier()) + { + scan_identifier(); + + if (current_char() == U('-')) + { + if (try_scan_guid(token_start)) + { + m_token = odata_expression_token::create_token(odata_expression_token_kind::GuidLiteral, extract_text(token_start), token_start); + return; + } + } + + auto text = extract_text(token_start); + + if (text == DoubleLiteralInf || text == DoubleLiteralNan) + { + m_token = odata_expression_token::create_token(odata_expression_token_kind::DoubleLiteral, text, token_start); + return; + } + + if (text == KeywordTrue || text == KeywordFalse) + { + m_token = odata_expression_token::create_token(odata_expression_token_kind::BooleanLiteral, text, token_start); + return; + } + + if (text == KeywordNull) + { + m_token = odata_expression_token::create_token(odata_expression_token_kind::NullLiteral, text, token_start); + return; + } + + auto kind = try_scan_quoted_literal(token_start); + if (kind != odata_expression_token_kind::None) + { + m_token = odata_expression_token::create_token(kind, extract_text(token_start), token_start); + return; + } + + m_token = odata_expression_token::create_token(odata_expression_token_kind::Identifier, text, token_start); + return; + } + + if (::odata::utility::is_digit(current_char())) + { + auto kind = scan_number(); + m_token = odata_expression_token::create_token(kind, extract_text(token_start), token_start); + return; + } + + throw odata_exception(ERROR_MESSAGE_INVALID_CHARACTER(current_char())); +} + +std::shared_ptr odata_expression_lexer::peek_token() +{ + auto orig_pos = m_pos; + auto orig_token = m_token; + + next_token(); + + auto next = current_token(); + + m_pos = orig_pos; + m_token = orig_token; + + return next; +} + +std::shared_ptr odata_expression_lexer::current_token() +{ + if (m_token == nullptr) + { + next_token(); + } + + return m_token; +} + +std::shared_ptr odata_expression_lexer::eat_token(int expected_kind, const ::odata::utility::string_t &_expected_token) +{ + auto token = current_token(); + + if (current_token()->token_kind() != expected_kind) + { + throw odata_exception(ERROR_MESSAGE_TOKEN_KIND_EXPECTED(_expected_token)); + } + + next_token(); + + return token; +} + +::odata::utility::string_t odata_expression_lexer::eat_identifier() +{ + auto token = current_token(); + + if (!token->is_identifier()) + { + throw odata_exception(ERROR_MESSAGE_TOKEN_KIND_EXPECTED(TokenKindIdentifier)); + } + + next_token(); + + return token->text(); +} + +::odata::utility::string_t odata_expression_lexer::eat_dotted_identifier() +{ + auto identifier = eat_identifier(); + + while (current_token()->token_kind() == odata_expression_token_kind::Dot) + { + next_token(); + + identifier += TokenDot + eat_identifier(); + } + + return identifier; +} + +std::shared_ptr<::odata::core::odata_primitive_value> odata_expression_lexer::eat_literal() +{ + auto token = current_token(); + + if (!token->is_literal()) + { + throw odata_exception(ERROR_MESSAGE_TOKEN_KIND_EXPECTED(TokenKindLiteral)); + } + + next_token(); + + return token->to_primitive_value(); +} + +::odata::utility::string_t odata_expression_lexer::advance_through_balanced_parenthesis() +{ + eat_token(odata_expression_token_kind::LeftParen, TokenLeftParen); + + ::odata::utility::string_t text; + auto start = m_pos; + + ::size_t depth = 1; + while (depth > 0) + { + if (current_char() == U('\'')) + { + scan_string(); + } + + if (current_char() == U('(')) + { + ++depth; + } + else if (current_char() == U(')')) + { + if (depth == 0) + { + throw odata_exception(ERROR_MESSAGE_PARENTHESIS_MISMATCH()); + } + + --depth; + + if (depth == 0) + { + text = extract_text(start); + } + } + else if (current_char() == 0) + { + throw odata_exception(ERROR_MESSAGE_PARENTHESIS_MISMATCH()); + } + + next_char(); + } + + next_token(); + + return std::move(text); +} + +void odata_expression_lexer::next_char() +{ + ::size_t new_pos = m_pos + 1; + + if (new_pos <= m_expression.size()) + { + m_pos = new_pos; + } +} + +::odata::utility::char_t odata_expression_lexer::eat_char(std::function validator, const ::odata::utility::string_t &_expected_char) +{ + auto _current_char = current_char(); + if (!validator(_current_char)) + { + throw odata_exception(ERROR_MESSAGE_CHAR_KIND_EXPECTED(_expected_char)); + } + + next_char(); + + return _current_char; +} + +void odata_expression_lexer::scan_whitespaces() +{ + while (current_char() == U(' ')) + { + next_char(); + } +} + +::size_t odata_expression_lexer::scan_digits() +{ + ::size_t count = 0; + + while (::odata::utility::is_digit(current_char())) + { + ++count; + next_char(); + } + + return count; +} + +::size_t odata_expression_lexer::scan_hex_digits() +{ + ::size_t count = 0; + + while (::odata::utility::is_hex_digit(current_char())) + { + ++count; + next_char(); + } + + return count; +} + +void odata_expression_lexer::scan_identifier() +{ + while (::odata::utility::is_letter_or_digit(current_char())) + { + next_char(); + } +} + +void odata_expression_lexer::scan_json_object_or_array() +{ + ::odata::utility::char_t close_char = current_char() == U('{') ? U('}') : U(']'); + + // Move to next close char or EOF. + while (current_char() != 0 && current_char() != close_char) + { + next_char(); + } + + // If EOF met before close char, throw. + if (!current_char()) + { + throw odata_exception(ERROR_MESSAGE_CLOSE_CHAR_EXPECTED(close_char)); + } + + // Skip close char. + next_char(); +} + +void odata_expression_lexer::scan_string() +{ + while (current_char() == U('\'')) + { + // Skip the current quote. + next_char(); + + // Move to next quote or EOF. + while (current_char() != 0 && current_char() != U('\'')) + { + next_char(); + } + + // If EOF met before string closes, throw. + if (!current_char()) + { + throw odata_exception(ERROR_MESSAGE_CLOSE_CHAR_EXPECTED(U('\''))); + } + + // Skip the current quote. + next_char(); + } +} + +int odata_expression_lexer::scan_number() +{ + auto token_start = m_pos; + + if (current_char() == U('-')) + { + // Skip - + next_char(); + } + + eat_char(::odata::utility::is_digit, CharKindDigit); + + scan_digits(); + + // First assume it to be an integer. + auto kind = determine_best_int_kind(token_start); + + if (current_char() == U('-')) + { + if (try_scan_datetimeoffset(token_start)) + { + return odata_expression_token_kind::DateTimeOffsetLiteral; + } + + if (try_scan_guid(token_start)) + { + return odata_expression_token_kind::GuidLiteral; + } + } + + // Guid could contain letters as well. + if (::odata::utility::is_letter(current_char())) + { + if (try_scan_guid(token_start)) + { + return odata_expression_token_kind::GuidLiteral; + } + } + + if (current_char() == U('.')) + { + // Skip dot. + next_char(); + + eat_char(::odata::utility::is_digit, CharKindDigit); + scan_digits(); + + // Dot met. Assume it to be a decimal or double or float number. + kind = determine_best_decimal_kind(token_start, true); + } + + // Handle exponents. + if (current_char() == U('e') || current_char() == U('E')) + { + // Skip e or E. + next_char(); + + if (current_char() == U('+') || current_char() == U('-')) + { + // Skip exponent sign. + next_char(); + } + + eat_char(::odata::utility::is_digit, CharKindDigit); + scan_digits(); + + // Dot met. Assume it to be a double or float number. + kind = determine_best_decimal_kind(token_start, false); + } + + // Shall we make a best guess before returning the kind? + return kind; +} + +#define SCAN_CHAR(CHAR) \ + do \ + { \ + if (current_char() != (CHAR)) \ + { \ + m_pos = orig_pos; \ + return false; \ + } \ + \ + next_char(); \ + } \ + while (false) + +#define SCAN_DASH() SCAN_CHAR(U('-')) +#define SCAN_T() SCAN_CHAR(U('T')) +#define SCAN_COLON() SCAN_CHAR(U(':')) + +#define SCAN_M_N_DIGITS(M, N) \ + do \ + { \ + auto count = scan_digits(); \ + if (count < (M) || count > (N)) \ + { \ + m_pos = orig_pos; \ + return false; \ + } \ + } \ + while (false) + +#define SCAN_N_DIGITS(N) SCAN_M_N_DIGITS((N), (N)) +#define SCAN_N_INF_DIGITS(N) SCAN_M_N_DIGITS((N), SIZE_MAX) + +bool odata_expression_lexer::try_scan_datetimeoffset(::size_t start) +{ + auto orig_pos = m_pos; + m_pos = start; + + if (current_char() == U('-')) + { + // Skip - + next_char(); + } + + // Scan year. + SCAN_N_INF_DIGITS(4); + SCAN_DASH(); + + // Scan month. + SCAN_N_DIGITS(2); + SCAN_DASH(); + + // Scan day. + SCAN_N_DIGITS(2); + SCAN_T(); + + // Scan hour. + SCAN_N_DIGITS(2); + SCAN_COLON(); + + // Scan minute. + SCAN_N_DIGITS(2); + + if (current_char() == U(':')) + { + // Skip : + next_char(); + + // Scan second. + SCAN_N_DIGITS(2); + + if (current_char() == U('.')) + { + // Skip dot + next_char(); + + SCAN_M_N_DIGITS(1, 12); + } + } + + if (current_char() == U('Z')) + { + // Skip Z + next_char(); + + return true; + } + + // Must be a sign. + if (current_char() == U('+') || current_char() == U('-')) + { + // Skip sign. + next_char(); + } + else + { + return false; + } + + // Scan hour. + SCAN_N_DIGITS(2); + SCAN_COLON(); + + // Scan minute. + SCAN_N_DIGITS(2); + + return true; +} + +#define SCAN_N_HEX_DIGITS(N) \ + do \ + { \ + if (scan_hex_digits() != (N)) \ + { \ + m_pos = orig_pos; \ + return false; \ + } \ + } \ + while (false) + +bool odata_expression_lexer::try_scan_guid(::size_t start) +{ + auto orig_pos = m_pos; + m_pos = start; + + SCAN_N_HEX_DIGITS(8); + SCAN_DASH(); + + for (int k = 0; k < 3; ++k) + { + SCAN_N_HEX_DIGITS(4); + SCAN_DASH(); + } + + SCAN_N_HEX_DIGITS(12); + + return true; +} + +int odata_expression_lexer::try_scan_quoted_literal(::size_t start) +{ + int kind = odata_expression_token_kind::None; + + if (current_char() == U('\'')) + { + auto text = extract_text(start); + + if (text == LiteralPrefixDuration) + { + kind = odata_expression_token_kind::DurationLiteral; + } + else if (text == LiteralPrefixBinary) + { + kind = odata_expression_token_kind::BinaryLiteral; + } + else + { + // For other unsupported quoted literals. + kind = odata_expression_token_kind::QuotedLiteral; + } + + if (kind != odata_expression_token_kind::None) + { + scan_string(); + } + } + + return kind; +} + +int odata_expression_lexer::determine_best_int_kind(::size_t start) const +{ + ::odata::utility::string_t text = extract_text(start); + + auto int64_value = ::odata::utility::conversions::scan_string(text); + if (int64_value < INT32_MIN || int64_value > INT32_MAX) + { + return odata_expression_token_kind::Int64Literal; + } + + return odata_expression_token_kind::Int32Literal; +} + +int odata_expression_lexer::determine_best_decimal_kind(::size_t start, bool can_be_decimal) const +{ + if (can_be_decimal) + { + return odata_expression_token_kind::DecimalLiteral; + } + + ::odata::utility::string_t text = extract_text(start); + + auto dbl_value = ::odata::utility::conversions::scan_string(text); + + if (dbl_value < FLT_MIN || dbl_value > FLT_MAX) + { + return odata_expression_token_kind::DoubleLiteral; + } + + return odata_expression_token_kind::SingleLiteral; +} + +// --END-- odata_expression_lexer + +// --BEGIN-- odata_expression_parser + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_expression(const ::odata::utility::string_t &expression, int parser_subject) +{ + auto lexer = odata_expression_lexer::create_lexer(expression); + return parse_expression(lexer, parser_subject); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_expression(std::shared_ptr lexer, int parser_subject) +{ + auto parser = std::make_shared(lexer, parser_subject); + return parser->parse_logical_or(); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_logical_or() +{ + auto node = parse_logical_and(); + while (lexer()->current_token()->is_identifier(KeywordOr)) + { + // Skip 'or' + lexer()->next_token(); + + node = odata_query_node::create_operator_or_node(node, parse_logical_and()); + } + + return node; +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_logical_and() +{ + auto node = parse_comparison(); + while (lexer()->current_token()->is_identifier(KeywordAnd)) + { + // Skip 'and' + lexer()->next_token(); + + node = odata_query_node::create_operator_and_node(node, parse_comparison()); + } + + return node; +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_comparison() +{ + auto node = parse_additive(); + + while (lexer()->current_token()->is_identifier()) + { + if (lexer()->current_token()->text() == KeywordEqual) + { + lexer()->next_token(); + node = odata_query_node::create_operator_eq_node(node, parse_additive()); + } + else if (lexer()->current_token()->text() == KeywordNotEqual) + { + lexer()->next_token(); + node = odata_query_node::create_operator_ne_node(node, parse_additive()); + } + else if (lexer()->current_token()->text() == KeywordGreaterThan) + { + lexer()->next_token(); + node = odata_query_node::create_operator_gt_node(node, parse_additive()); + } + else if (lexer()->current_token()->text() == KeywordGreaterThanOrEqual) + { + lexer()->next_token(); + node = odata_query_node::create_operator_ge_node(node, parse_additive()); + } + else if (lexer()->current_token()->text() == KeywordLessThan) + { + lexer()->next_token(); + node = odata_query_node::create_operator_lt_node(node, parse_additive()); + } + else if (lexer()->current_token()->text() == KeywordLessThanOrEqual) + { + lexer()->next_token(); + node = odata_query_node::create_operator_le_node(node, parse_additive()); + } + else if (lexer()->current_token()->text() == KeywordHas) + { + lexer()->next_token(); + node = odata_query_node::create_operator_has_node(node, parse_additive()); + } + else + { + break; + } + } + + return node; +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_additive() +{ + auto node = parse_multiplicative(); + + while (lexer()->current_token()->is_identifier()) + { + if (lexer()->current_token()->text() == KeywordAdd) + { + lexer()->next_token(); + node = odata_query_node::create_operator_add_node(node, parse_multiplicative()); + } + else if (lexer()->current_token()->text() == KeywordSub) + { + lexer()->next_token(); + node = odata_query_node::create_operator_sub_node(node, parse_multiplicative()); + } + else + { + break; + } + } + + return node; +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_multiplicative() +{ + auto node = parse_unary(); + + while (lexer()->current_token()->is_identifier()) + { + if (lexer()->current_token()->text() == KeywordMultiply) + { + lexer()->next_token(); + node = odata_query_node::create_operator_mul_node(node, parse_unary()); + } + else if (lexer()->current_token()->text() == KeywordDivide) + { + lexer()->next_token(); + node = odata_query_node::create_operator_div_node(node, parse_unary()); + } + else if (lexer()->current_token()->text() == KeywordModulo) + { + lexer()->next_token(); + node = odata_query_node::create_operator_mod_node(node, parse_unary()); + } + else + { + break; + } + } + + return node; +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_unary() +{ + if (lexer()->current_token()->token_kind() == odata_expression_token_kind::Minus) + { + lexer()->next_token(); + return odata_query_node::create_operator_neg_node(parse_unary()); + } + else if (lexer()->current_token()->is_identifier(KeywordNot)) + { + lexer()->next_token(); + return odata_query_node::create_operator_not_node(parse_unary()); + } + + return parse_postfix(); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_star( + std::shared_ptr<::odata::core::odata_query_node> parent) +{ + return odata_query_node::create_property_access_node(TokenStar, parent); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_postfix() +{ + std::shared_ptr<::odata::core::odata_query_node> node; + + if (lexer()->peek_token()->token_kind() == odata_expression_token_kind::Slash) + { + node = parse_identifier(nullptr); + } + else + { + return parse_primary(); + } + + do + { + // Skip '/' + lexer()->next_token(); + + auto current = lexer()->current_token(); + auto next = lexer()->peek_token(); + if (current->is_identifier(KeywordAll) || current->is_identifier(KeywordAny)) + { + node = parse_lambda(node); + } + else if (next->token_kind() == odata_expression_token_kind::Slash) + { + // In path + node = parse_identifier(node); + } + else + { + // End of path + if (current->token_kind() == odata_expression_token_kind::Star) + { + node = parse_star(node); + } + else + { + node = parse_identifier(node); + } + } + } + while (lexer()->current_token()->token_kind() == odata_expression_token_kind::Slash); + + return node; +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_primary() +{ + switch (lexer()->current_token()->token_kind()) + { + case odata_expression_token_kind::Star: + return parse_star(nullptr); + + case odata_expression_token_kind::ParameterAlias: + return parse_parameter_alias(); + + case odata_expression_token_kind::LeftParen: + return parse_parenthesis(); + + case odata_expression_token_kind::Identifier: + // Path of single identifier like: + // $filter=Items + return parse_identifier(nullptr); + + default: + return parse_literal(); + } +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_parameter_alias() +{ + auto token = lexer()->eat_token(odata_expression_token_kind::ParameterAlias, TokenKindParameterAlias); + + return odata_query_node::create_parameter_alias_node(token->text()); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_parenthesis() +{ + lexer()->eat_token(odata_expression_token_kind::LeftParen, TokenLeftParen); + + auto node = parse_logical_or(); + + lexer()->eat_token(odata_expression_token_kind::RightParen, TokenRightParen); + + return node; +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_literal() +{ + return odata_query_node::create_constant_node(lexer()->eat_literal()); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_lambda( + std::shared_ptr<::odata::core::odata_query_node> parent) +{ + auto identifier = lexer()->eat_identifier(); + + bool is_any = identifier == KeywordAny; + + lexer()->eat_token(odata_expression_token_kind::LeftParen, TokenLeftParen); + + auto parameter = lexer()->eat_identifier(); + + if (m_lambda_parameters.find(parameter) != m_lambda_parameters.end()) + { + throw odata_exception(ERROR_MESSAGE_DUPLICATE_RANGE_VARIABLE(parameter)); + } + + m_lambda_parameters.insert(parameter); + + lexer()->eat_token(odata_expression_token_kind::Colon, TokenColon); + + auto expression = parse_logical_or(); + + lexer()->eat_token(odata_expression_token_kind::RightParen, TokenRightParen); + + // forget about the range variable after parsing the expression for this lambda. + m_lambda_parameters.erase(parameter); + + return odata_query_node::create_lambda_node(is_any, expression, parameter, parent); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::parse_identifier( + std::shared_ptr<::odata::core::odata_query_node> parent) +{ + auto identifier = lexer()->eat_dotted_identifier(); + + if (m_parser_subject == odata_expression_parser_subject::SearchClause) + { + // If we are parsing search expression, treat identifiers as string literals. + return odata_query_node::create_constant_node(::odata::core::odata_primitive_value::make_primitive_value(identifier)); + } + + // Parse function call (not available in $expand). + if (lexer()->current_token()->token_kind() == odata_expression_token_kind::LeftParen + && m_parser_subject != odata_expression_parser_subject::ExpandClause) + { + return expand_to_function_call(identifier, parent); + } + + // Identifier is one of the lambda parameters & at top level (not property name). + if (m_lambda_parameters.find(identifier) != m_lambda_parameters.end() && parent == nullptr) + { + return odata_query_node::create_range_variable_node(identifier); + } + + if (identifier.find(U('.')) != ::odata::utility::string_t::npos) + { + // Dotted identifier is type cast. + return odata_query_node::create_type_cast_node(identifier, parent); + } + + return odata_query_node::create_property_access_node(identifier, parent); +} + +std::shared_ptr<::odata::core::odata_query_node> odata_expression_parser::expand_to_function_call( + const ::odata::utility::string_t &identifier, + std::shared_ptr<::odata::core::odata_query_node> parent) +{ + std::vector>> parameters; + + lexer()->eat_token(odata_expression_token_kind::LeftParen, TokenLeftParen); + + if (lexer()->current_token()->token_kind() == odata_expression_token_kind::RightParen) + { + // Empty function parameters. + lexer()->next_token(); + } + else + { + parameters.push_back(parse_named_value()); + + while (lexer()->current_token()->token_kind() == odata_expression_token_kind::Comma) + { + // Skip ',' + lexer()->next_token(); + + parameters.push_back(parse_named_value()); + } + + lexer()->eat_token(odata_expression_token_kind::RightParen, TokenRightParen); + } + + return odata_query_node::create_function_call_node(identifier, std::move(parameters)); +} + +std::pair<::odata::utility::string_t, std::shared_ptr<::odata::core::odata_query_node>> +odata_expression_parser::parse_named_value() +{ + ::odata::utility::string_t name; + + if (lexer()->peek_token()->token_kind() == odata_expression_token_kind::Equal) + { + name = lexer()->eat_identifier(); + lexer()->next_token(); + } + + auto value = parse_logical_or(); + + return std::make_pair(name, value); +} + +// --END-- odata_expression_parser + +// --BEGIN-- odata_uri_parser + +std::shared_ptr<::odata::core::odata_path> odata_uri_parser::parse_path(const ::odata::utility::string_t& path) +{ + return path_parser()->parse_path(path); +} + +std::shared_ptr<::odata::core::odata_select_expand_clause> odata_uri_parser::parse_select_and_expand( + const ::odata::utility::string_t& select_query, const ::odata::utility::string_t& expand_query) +{ + return query_option_parser()->parse_select_and_expand(select_query, expand_query); +} + +std::shared_ptr<::odata::core::odata_filter_clause> odata_uri_parser::parse_filter(const ::odata::utility::string_t& filter_query) +{ + return query_option_parser()->parse_filter(filter_query); +} + +std::shared_ptr<::odata::core::odata_orderby_clause> odata_uri_parser::parse_orderby( + const ::odata::utility::string_t& orderby_query) +{ + return query_option_parser()->parse_orderby(orderby_query); +} + +std::shared_ptr<::odata::core::odata_search_clause> odata_uri_parser::parse_search(const ::odata::utility::string_t& search_query) +{ + return query_option_parser()->parse_search(search_query); +} + +::odata::common::nullable odata_uri_parser::parse_top(const ::odata::utility::string_t& top_query) +{ + return query_option_parser()->parse_top(top_query); +} + +::odata::common::nullable odata_uri_parser::parse_skip(const ::odata::utility::string_t& skip_query) +{ + return query_option_parser()->parse_skip(skip_query); +} + +::odata::common::nullable odata_uri_parser::parse_count(const ::odata::utility::string_t& count_query) +{ + return query_option_parser()->parse_count(count_query); +} + +std::shared_ptr<::odata::core::odata_uri> odata_uri_parser::parse_uri(const ::odata::utility::uri &uri) +{ + auto path = parse_path(::odata::utility::uri::decode(uri.path())); + + auto query_options = split_query_options(uri.query()); + + ::odata::utility::string_t select_query; + ::odata::utility::string_t expand_query; + ::odata::utility::string_t filter_query; + ::odata::utility::string_t orderby_query; + ::odata::utility::string_t search_query; + ::odata::utility::string_t top_query; + ::odata::utility::string_t skip_query; + ::odata::utility::string_t count_query; + + for (auto iter = query_options.begin(); iter != query_options.end(); ++iter) + { + auto &option = *iter; + + if (option.first == QueryOptionSelect) + { + select_query = ::odata::utility::uri::decode(option.second); + } + else if (option.first == QueryOptionExpand) + { + expand_query = ::odata::utility::uri::decode(option.second); + } + else if (option.first == QueryOptionFilter) + { + filter_query = ::odata::utility::uri::decode(option.second); + } + else if (option.first == QueryOptionOrderBy) + { + orderby_query = ::odata::utility::uri::decode(option.second); + } + else if (option.first == QueryOptionSearch) + { + search_query = ::odata::utility::uri::decode(option.second); + } + else if (option.first == QueryOptionTop) + { + top_query = ::odata::utility::uri::decode(option.second); + } + else if (option.first == QueryOptionSkip) + { + skip_query = ::odata::utility::uri::decode(option.second); + } + else if (option.first == QueryOptionCount) + { + count_query = ::odata::utility::uri::decode(option.second); + } + } + + auto select_expand_clause = parse_select_and_expand(select_query, expand_query); + auto filter_clause = parse_filter(filter_query); + auto orderby_clause = parse_orderby(orderby_query); + auto search_clause = parse_search(search_query); + auto top = parse_top(top_query); + auto skip = parse_skip(skip_query); + auto count = parse_count(count_query); + + return ::odata::core::odata_uri::create_uri( + path, + select_expand_clause, + filter_clause, + orderby_clause, + search_clause, + top, + skip, + count); +} + +::size_t odata_uri_parser::advance_through_string_literal(const ::odata::utility::string_t &query, ::size_t start) +{ + auto index = start; + + while (index < query.size() && query[index] == U('\'')) + { + ++index; + + while (index < query.size() && query[index] != U('\'')) + { + ++index; + } + + if (index >= query.size()) + { + throw odata_exception(ERROR_MESSAGE_CLOSE_CHAR_EXPECTED(U('\''))); + } + + ++index; + } + + return index; +} + +::size_t odata_uri_parser::advance_through_balanced_parenthesis(const ::odata::utility::string_t &query, ::size_t start) +{ + auto index = start; + + if (query[index] == U('(')) + { + ++index; + + ::size_t depth = 1; + + while (index < query.size() && depth > 0) + { + if (query[index] == U('\'')) + { + index = advance_through_string_literal(query, index); + continue; + } + else if (query[index] == U('(')) + { + ++depth; + } + else if (query[index] == U(')')) + { + if (depth == 0) + { + throw odata_exception(ERROR_MESSAGE_PARENTHESIS_MISMATCH()); + } + + --depth; + } + + ++index; + } + + if (depth > 0) + { + throw odata_exception(ERROR_MESSAGE_PARENTHESIS_MISMATCH()); + } + } + + return index; +} + +std::map<::odata::utility::string_t, ::odata::utility::string_t> odata_uri_parser::split_query_options(const ::odata::utility::string_t &query, bool is_semicolon_separated) +{ + std::map<::odata::utility::string_t, ::odata::utility::string_t> options; + + if (query.empty()) + { + return options; + } + + ::odata::utility::char_t delim = is_semicolon_separated ? U(';') : U('&'); + + ::odata::utility::string_t name; + ::odata::utility::string_t value; + + ::size_t token_start = 0; + ::size_t index = 0; + while (index < query.size()) + { + if (query[index] == U('\'')) + { + index = advance_through_string_literal(query, index); + continue; + } + else if (query[index] == U('(')) + { + index = advance_through_balanced_parenthesis(query, index); + continue; + } + else if (query[index] == delim) + { + value = query.substr(token_start, index - token_start); + token_start = index + 1; + + options.insert(std::make_pair(std::move(name), std::move(value))); + } + else if (query[index] == U('=')) + { + name = query.substr(token_start, index - token_start); + token_start = index + 1; + } + + ++index; + } + + value = query.substr(token_start); + options.insert(std::make_pair(std::move(name), std::move(value))); + + return std::move(options); +} + +std::shared_ptr odata_uri_parser::path_parser() +{ + if (m_path_parser == nullptr) + { + m_path_parser = std::make_shared(model()); + } + + return m_path_parser; +} + +std::shared_ptr odata_uri_parser::query_option_parser() +{ + if (m_query_option_parser == nullptr) + { + m_query_option_parser = std::make_shared( + model(), + path_parser()->target_type(), + path_parser()->target_navigation_source()); + } + + return m_query_option_parser; +} + +// --END-- odata_uri_parser + +}} \ No newline at end of file diff --git a/src/edm/edm_entity_container.cpp b/src/edm/edm_entity_container.cpp new file mode 100644 index 0000000..75ef5dc --- /dev/null +++ b/src/edm/edm_entity_container.cpp @@ -0,0 +1,45 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/edm/odata_edm.h" + +namespace odata { namespace edm +{ + +std::shared_ptr edm_entity_container::find_entity_set(::odata::utility::string_t name) const +{ + auto find_iter = m_entity_sets.find(name); + if (find_iter != m_entity_sets.end()) + { + return find_iter->second; + } + + return nullptr; +} + +std::shared_ptr edm_entity_container::find_singleton(::odata::utility::string_t name) const +{ + auto find_iter = m_singletons.find(name); + if (find_iter != m_singletons.end()) + { + return find_iter->second; + } + + return nullptr; +} + +std::shared_ptr edm_entity_container::find_operation_import(::odata::utility::string_t name) const +{ + auto find_iter = m_operation_imports.find(name); + if (find_iter != m_operation_imports.end()) + { + return find_iter->second; + } + + return nullptr; +} + +}} \ No newline at end of file diff --git a/src/edm/edm_model.cpp b/src/edm/edm_model.cpp new file mode 100644 index 0000000..e11480f --- /dev/null +++ b/src/edm/edm_model.cpp @@ -0,0 +1,108 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/edm/odata_edm.h" + +namespace odata { namespace edm +{ + +std::shared_ptr edm_schema::find_container(::odata::utility::string_t name) const +{ + if (name.empty()) + { + for (auto ent = m_entity_containers.begin(); ent != m_entity_containers.end(); ++ent) + { + if (ent->second->is_default_container()) + { + return ent->second; + } + } + } + else + { + for (auto ent = m_entity_containers.begin(); ent != m_entity_containers.end(); ++ent) + { + if (ent->second->get_name() == name) + { + return ent->second; + } + } + } + + return nullptr; +} + +std::shared_ptr edm_model::find_entity_type(::odata::utility::string_t name) const +{ + for (auto sc = m_schemata.cbegin(); sc != m_schemata.cend(); ++sc) + { + auto et_ptr = (*sc)->find_entity_type(name); + if (et_ptr) + { + return et_ptr; + } + } + + return nullptr; +} + +std::shared_ptr edm_model::find_complex_type(::odata::utility::string_t name) const +{ + for (auto sc = m_schemata.cbegin(); sc != m_schemata.cend(); ++sc) + { + auto cp_ptr = (*sc)->find_complex_type(name); + if (cp_ptr) + { + return cp_ptr; + } + } + + return nullptr; +} + +std::shared_ptr edm_model::find_enum_type(::odata::utility::string_t name) const +{ + for (auto sc = m_schemata.cbegin(); sc != m_schemata.cend(); ++sc) + { + auto en_ptr = (*sc)->find_enum_type(name); + if (en_ptr) + { + return en_ptr; + } + } + + return nullptr; +} + +std::shared_ptr edm_model::find_operation_type(::odata::utility::string_t name) const +{ + for (auto sc = m_schemata.cbegin(); sc != m_schemata.cend(); ++sc) + { + auto op_ptr = (*sc)->find_operation_type(name); + if (op_ptr) + { + return op_ptr; + } + } + + return nullptr; +} + +std::shared_ptr edm_model::find_container(::odata::utility::string_t name) const +{ + for (auto sc = m_schemata.cbegin(); sc != m_schemata.cend(); ++sc) + { + auto cn_ptr = (*sc)->find_container(name); + if (cn_ptr) + { + return cn_ptr; + } + } + + return nullptr; +} + +}} \ No newline at end of file diff --git a/src/edm/edm_model_reader.cpp b/src/edm/edm_model_reader.cpp new file mode 100644 index 0000000..5908af9 --- /dev/null +++ b/src/edm/edm_model_reader.cpp @@ -0,0 +1,571 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/edm/edm_model_reader.h" +#include "odata/edm/edm_model_utility.h" + +namespace odata { namespace edm +{ +bool edm_model_reader::parse() +{ + bool ret = ::odata::edm::xml_reader::parse(); + + edm_model_utility::resolve_edm_types_after_parsing(m_model); + + return ret; +} + +void edm_model_reader::handle_begin_element(const ::odata::utility::string_t& elementName) +{ + if (elementName == U("Edmx")) + { + while (move_to_next_attribute()) + { + if(get_current_element_name() == U("Version")) + { + m_model->set_version(get_current_element_text()); + } + } + } + else if (elementName == U("Schema")) + { + ::odata::utility::string_t namesp; + ::odata::utility::string_t alias; + + while (move_to_next_attribute()) + { + if(get_current_element_name() == U("Namespace")) + { + namesp = get_current_element_text(); + } + + if(get_current_element_name() == U("Alias")) + { + alias = get_current_element_text(); + } + } + + m_current_schema = m_model->add_schema(namesp, alias); + } + else if (elementName == U("EntityContainer")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t extends; + bool is_default = true; + + while (move_to_next_attribute()) + { + if (get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("Extends")) + { + extends = get_current_element_text(); + } + } + + m_current_container = std::make_shared(name, is_default); + } + else if (elementName == U("EntitySet")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t type; + + while (move_to_next_attribute()) + { + if(get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("EntityType")) + { + type = get_current_element_text(); + } + } + + m_current_entity_set = std::make_shared(name, type); + } + else if (elementName == U("Singleton")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t type; + + while (move_to_next_attribute()) + { + if(get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("Type")) + { + type = get_current_element_text(); + } + } + + m_current_singleton = std::make_shared(name, type); + } + else if (elementName == U("FunctionImport") || elementName == U("ActionImport")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t entity_set_path; + bool is_in_service_document; + OperationImportKind operation_import_kind = elementName == U("FunctionImport") ? OperationImportKind::FunctionImport : OperationImportKind::ActionImport; + ::odata::utility::string_t operation_name; + + while (move_to_next_attribute()) + { + if(get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("EntitySet")) + { + entity_set_path = get_current_element_text(); + } + else if (get_current_element_name() == U("IncludeInServiceDocument")) + { + is_in_service_document = get_current_element_text() == U("true") ? true : false;; + } + else if (get_current_element_name() == U("Function") || get_current_element_name() == U("Action")) + { + operation_name = get_current_element_text(); + } + } + + if (m_current_container) + { + m_current_container->add_operation_import(std::make_shared(name, operation_name, entity_set_path, is_in_service_document, operation_import_kind)); + } + } + else if (elementName == U("EntityType")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t baseType; + bool isAbstract = false; + bool isOpenType = false; + bool hasStream = false; + + while (move_to_next_attribute()) + { + if (get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("BaseType")) + { + baseType = get_current_element_text(); + } + else if (get_current_element_name() == U("Abstract")) + { + isAbstract = get_current_element_text() == U("true") ? true : false; + } + else if (get_current_element_name() == U("OpenType")) + { + isOpenType = get_current_element_text() == U("true") ? true : false; + } + else if (get_current_element_name() == U("HasStream")) + { + hasStream = get_current_element_text() == U("true") ? true : false; + } + } + + m_current_st = new edm_entity_type(name, m_current_schema->get_name(), baseType, isAbstract, isOpenType, hasStream); + } + else if (elementName == U("ComplexType")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t baseType; + bool isAbstract = false; + bool isOpenType = false; + + while (move_to_next_attribute()) + { + if (get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("BaseType")) + { + baseType = get_current_element_text(); + } + else if (get_current_element_name() == U("Abstract")) + { + isAbstract = get_current_element_text() == U("true") ? true : false; + } + else if (get_current_element_name() == U("OpenType")) + { + isOpenType = get_current_element_text() == U("true") ? true : false; + } + } + + m_current_st = new edm_complex_type(name, m_current_schema->get_name(), baseType, isAbstract, isOpenType); + } + else if (elementName == U("EnumType")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t underlying_type = U("Edm.Int32"); + bool is_flag = false; + + while (move_to_next_attribute()) + { + if (get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("UnderlyingType")) + { + underlying_type = get_current_element_text(); + } + else if (get_current_element_name() == U("IsFlags")) + { + is_flag = get_current_element_text() == U("true") ? true : false; + } + } + + m_current_enum = new edm_enum_type(name, m_current_schema->get_name(), underlying_type, is_flag); + } + else if (elementName == U("Function") || elementName == U("Action")) + { + ::odata::utility::string_t name; + ::odata::utility::string_t path; + bool is_bound = false; + bool is_composable = false; + EdmOperationKind operation_kind = elementName == U("Function") ? EdmOperationKind::Function : EdmOperationKind::Action; + + while (move_to_next_attribute()) + { + if (get_current_element_name() == U("Name")) + { + name = get_current_element_text(); + } + else if (get_current_element_name() == U("EntitySetPath")) + { + path = get_current_element_text(); + } + else if (get_current_element_name() == U("IsBound")) + { + is_bound = get_current_element_text() == U("true") ? true : false; + } + else if (get_current_element_name() == U("IsComposable")) + { + is_composable = get_current_element_text() == U("true") ? true : false; + } + } + + m_current_operation = new edm_operation_type(name, m_current_schema->get_name(), is_bound, path, operation_kind, is_composable); + } + else if (elementName == U("Property") || elementName == U("Member")) + { + _process_property(); + } + else if (elementName == U("NavigationPropertyBinding")) + { + _process_navigation_property_binding(); + } + else if (elementName == U("NavigationProperty")) + { + _process_navigation_property(); + } + else if (elementName == U("Parameter")) + { + _process_operation_parameter(); + } + else if (elementName == U("ReturnType")) + { + _process_operation_return_type(); + } + else if (m_parsing_key && elementName == U("PropertyRef")) + { + while (move_to_next_attribute()) + { + if (get_current_element_name() == U("Name")) + { + dynamic_cast(m_current_st)->add_key_property(get_current_element_text()); + break; + } + } + + } + else if (elementName == U("Key")) + { + m_parsing_key = true; + } +#ifdef WIN32 + m_reader->MoveToElement(); +#endif + +} + +void edm_model_reader::handle_end_element(const ::odata::utility::string_t& elementName) +{ + if (elementName == U("EntityContainer")) + { + m_current_schema->add_container(m_current_container); + m_current_container.reset(); + } + + if (elementName == U("EntityType")) + { + m_current_schema->add_entity_type(std::shared_ptr(static_cast(m_current_st))); + m_current_st = nullptr; + } + + if (elementName == U("ComplexType")) + { + m_current_schema->add_complex_type(std::shared_ptr(static_cast(m_current_st))); + m_current_st = nullptr; + } + + if (elementName == U("Key")) + { + m_parsing_key = false; + } + + if (elementName == U("EnumType")) + { + m_current_schema->add_enum_type(std::shared_ptr(static_cast(m_current_enum))); + m_current_enum = nullptr; + } + + if (elementName == U("Function") || elementName == U("Action")) + { + m_current_schema->add_operation_type(std::shared_ptr(static_cast(m_current_operation))); + m_current_operation = nullptr; + } + + if (elementName == U("EntitySet")) + { + if (m_current_container) + { + m_current_container->add_entity_set(m_current_entity_set); + } + + m_current_entity_set = nullptr; + } + + if (elementName == U("Singleton")) + { + if (m_current_container) + { + m_current_container->add_singleton(m_current_singleton); + } + + m_current_singleton = nullptr; + } +} + +void edm_model_reader::handle_element(const ::odata::utility::string_t&) +{ +} + +void edm_model_reader::_process_property() +{ + if (m_current_st) + { + ::odata::utility::string_t property_name; + bool is_nullable; + std::shared_ptr type; + unsigned int max_length = undefined_value; + bool is_unicode; + unsigned int scale = undefined_value; + unsigned int precision = undefined_value; + + while (move_to_next_attribute()) + { + auto name = get_current_element_name(); + auto value = get_current_element_text(); + + if (name == U("Name")) + { + property_name = value; + } + else if (name == U("Nullable")) + { + is_nullable = (value == U("true")); + } + else if (name == U("Type")) + { + type = edm_model_utility::get_edm_type_from_name(value); + + if (!type) + { + // we have to parse the real type after first round parsing + type = std::make_shared(value, U(""), edm_type_kind_t::Unknown); + } + } + else if (name == U("MaxLength")) + { + odata::utility::bind(value.c_str(), max_length); + } + else if (name == U("Unicode")) + { + is_unicode = (value == U("true")); + } + else if (name == U("Scale")) + { + odata::utility::bind(value.c_str(), scale); + } + else if (name == U("Precision")) + { + odata::utility::bind(value.c_str(), precision); + } + } + + auto prop = std::make_shared(property_name, is_nullable, max_length, is_unicode, scale); + prop->set_property_type(type); + prop->set_precision(precision); + m_current_st->add_property(prop); + } + else if (m_current_enum) + { + ::odata::utility::string_t enum_name; + unsigned long enum_value; + + while (move_to_next_attribute()) + { + auto name = get_current_element_name(); + auto value = get_current_element_text(); + + if (name == U("Name")) + { + enum_name = value; + } + else if (name == U("Value")) + { + odata::utility::bind(value.c_str(), enum_value); + } + } + + auto prop = std::make_shared(enum_name, enum_value); + m_current_enum->add_enum_member(prop); + } +} + +void edm_model_reader::_process_navigation_property() +{ + if (m_current_st) + { + ::odata::utility::string_t property_name; + ::odata::utility::string_t partner_name; + ::odata::utility::string_t type_name; + bool is_contained = false; + bool is_nullable = false; + + while (move_to_next_attribute()) + { + auto name = get_current_element_name(); + auto value = get_current_element_text(); + + if (name == U("Name")) + { + property_name = value; + } + else if (name == U("Nullable")) + { + is_nullable = (value == U("true")); + } + else if (name == U("Partner")) + { + partner_name = value; + } + else if (name == U("Type")) + { + type_name = value; + } + else if (name == U("ContainsTarget")) + { + is_contained = (value == U("true")); + } + } + + + auto type = std::make_shared(type_name, partner_name, is_contained); + auto prop = std::make_shared(property_name, is_nullable, type); + m_current_st->add_property(prop); + } +} + +void edm_model_reader::_process_navigation_property_binding() +{ + ::odata::utility::string_t property_path_name; + ::odata::utility::string_t target_name; + + while (move_to_next_attribute()) + { + auto name = get_current_element_name(); + auto value = get_current_element_text(); + + if (name == U("Path")) + { + property_path_name = value; + } + else if (name == U("Target")) + { + target_name = value; + } + } + + if (m_current_entity_set) + { + m_current_entity_set->add_navigation_source(property_path_name, target_name); + } + else if (m_current_singleton) + { + m_current_singleton->add_navigation_source(property_path_name, target_name); + } +} + +void edm_model_reader::_process_operation_parameter() +{ + if (m_current_operation) + { + ::odata::utility::string_t param_name; + std::shared_ptr param_type; + + while (move_to_next_attribute()) + { + auto name = get_current_element_name(); + auto value = get_current_element_text(); + + if (name == U("Name")) + { + param_name = value; + } + else if (name == U("Type")) + { + param_type = std::make_shared(value, U(""), edm_type_kind_t::Unknown); + } + } + + auto param = std::make_shared(param_name, param_type); + + m_current_operation->add_operation_parameter(param); + } +} + +void edm_model_reader::_process_operation_return_type() +{ + if (m_current_operation) + { + auto return_type = std::make_shared(); + + while (move_to_next_attribute()) + { + auto name = get_current_element_name(); + auto value = get_current_element_text(); + + if (name == U("Type")) + { + return_type = std::make_shared(value, U(""), edm_type_kind_t::Unknown); + } + } + + m_current_operation->set_return_type(return_type); + } +} + +}} diff --git a/src/edm/edm_model_utility.cpp b/src/edm/edm_model_utility.cpp new file mode 100644 index 0000000..b91d6d2 --- /dev/null +++ b/src/edm/edm_model_utility.cpp @@ -0,0 +1,748 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/edm/edm_model_utility.h" + +namespace odata { namespace edm +{ + +const std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type init_name_to_prmitive_value[] = +{ + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Binary"), edm_primitive_type::BINARY()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Boolean"), edm_primitive_type::BOOLEAN()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Byte"), edm_primitive_type::BYTE()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Duration"), edm_primitive_type::DURATION()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.DateTimeOffset"), edm_primitive_type::DATETIMEOFFSET()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Double"), edm_primitive_type::DOUBLE()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Decimal"), edm_primitive_type::DECIMAL()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Guid"), edm_primitive_type::GUID()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Int16"), edm_primitive_type::INT16()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Int32"), edm_primitive_type::INT32()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Int64"), edm_primitive_type::INT64()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.SByte"), edm_primitive_type::SBYTE()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Single"), edm_primitive_type::SINGLE()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.Stream"), edm_primitive_type::STREAM()), + std::unordered_map<::odata::utility::string_t, std::shared_ptr>::value_type(U("Edm.String"), edm_primitive_type::STRING()), + +}; + +const static std::unordered_map<::odata::utility::string_t, std::shared_ptr> name_to_edm_primitive_type_map(init_name_to_prmitive_value, init_name_to_prmitive_value + 15); + +const std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type init_edm_prmitive_type_name_to_strong_type_name[] = +{ + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Binary"), U("std::vector")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Boolean"), U("bool")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Byte"), U("uint8_t")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Duration"), U("::utility::seconds")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.DateTimeOffset"), U("::utility::datetime")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Double"), U("double")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Decimal"), U("long double")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Guid"), U("::odata::utility::string_t")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Int16"), U("int16_t")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Int32"), U("int32_t")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Int64"), U("int64_t")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.SByte"), U("int8_t")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Single"), U("float")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.String"), U("::odata::utility::string_t")), +}; + +const static std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> edm_prmitive_type_name_to_strong_type_name_map(init_edm_prmitive_type_name_to_strong_type_name, init_edm_prmitive_type_name_to_strong_type_name + 14); + +const std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type init_edm_prmitive_type_name_to_strong_type_default_value[] = +{ + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Binary"), U("")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Boolean"), U("false")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Byte"), U("0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Duration"), U("")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.DateTimeOffset"), U("")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Double"), U("0.0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Decimal"), U("0.0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Guid"), U("")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Int16"), U("0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Int32"), U("0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Int64"), U("0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.SByte"), U("0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.Single"), U("0.0")), + std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t>::value_type(U("Edm.String"), U("")), +}; + +const static std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> edm_prmitive_type_name_to_strong_type_default_value_map(init_edm_prmitive_type_name_to_strong_type_default_value, init_edm_prmitive_type_name_to_strong_type_default_value + 14); + +std::shared_ptr edm_model_utility::get_edm_primitive_type_from_name(const ::odata::utility::string_t& type_name) +{ + if (type_name.substr(0, 4) == U("Edm.")) + { + auto find_iter = name_to_edm_primitive_type_map.find(type_name); + if (find_iter != name_to_edm_primitive_type_map.end()) + { + return find_iter->second; + } + } + + return nullptr; +} + +::odata::utility::string_t edm_model_utility::get_strong_type_name_from_edm_type_name(const std::shared_ptr& primitive_type) +{ + ::odata::utility::string_t ret; + + auto find_iter = edm_prmitive_type_name_to_strong_type_name_map.find(primitive_type->get_name()); + if (find_iter != edm_prmitive_type_name_to_strong_type_name_map.end()) + { + ret = find_iter->second; + } + + return ret; +} + +::odata::utility::string_t edm_model_utility::get_strong_type_default_value_from_edm_type_name(const std::shared_ptr& primitive_type) +{ + ::odata::utility::string_t ret; + + auto find_iter = edm_prmitive_type_name_to_strong_type_default_value_map.find(primitive_type->get_name()); + if (find_iter != edm_prmitive_type_name_to_strong_type_default_value_map.end()) + { + ret = find_iter->second; + } + + return ret; +} + +std::shared_ptr edm_model_utility::get_edm_type_from_name(const ::odata::utility::string_t& type_name) +{ + if (type_name.substr(0, 4) == U("Edm.")) + { + return edm_model_utility::get_edm_primitive_type_from_name(type_name); + } + else if (type_name.substr(0, 10) == U("Collection")) + { + int index_first = type_name.find_first_of('(') + 1; + int index_last = type_name.find_last_of(')'); + ::odata::utility::string_t element_name = type_name.substr(index_first, index_last - index_first); + + auto element_type = edm_model_utility::get_edm_type_from_name(element_name); + if (!element_type) + { + element_type = std::make_shared(element_name, U(""), edm_type_kind_t::Unknown); + } + + auto collection = std::make_shared(element_type); + return collection; + } + else + { + return nullptr; + } +} + +::odata::utility::string_t edm_model_utility::get_type_kind_name_from_edm_type(const std::shared_ptr& ptype) +{ + switch(ptype->get_type_kind()) + { + case edm_type_kind_t::Primitive: + return ::odata::utility::string_t(U("Primitive Type.")); + case edm_type_kind_t::Collection: + return ::odata::utility::string_t(U("Collection Type.")); + case edm_type_kind_t::Complex: + return ::odata::utility::string_t(U("Complex Type.")); + case edm_type_kind_t::Enum: + return ::odata::utility::string_t(U("Enum Type.")); + case edm_type_kind_t::Entity: + return ::odata::utility::string_t(U("Entity Type.")); + case edm_type_kind_t::Navigation: + return ::odata::utility::string_t(U("Navigation Type.")); + case edm_type_kind_t::Operation: + return ::odata::utility::string_t(U("Operation Type.")); + default: + return ::odata::utility::string_t(U("Unknown Type.")); + } +} + +bool edm_model_utility::get_primitive_kind_from_edm_type(const std::shared_ptr& edm_type, edm_primitive_type_kind_t& primitive_kind) +{ + if (edm_type && edm_type->get_type_kind() == edm_type_kind_t::Primitive) + { + std::shared_ptr primitive_type = std::dynamic_pointer_cast(edm_type); + + if (primitive_type) + { + primitive_kind = primitive_type->get_primitive_kind(); + return true; + } + } + + return false; +} + +void edm_model_utility::resolve_edm_types_after_parsing(const std::shared_ptr& model) +{ + if (!model) + { + return ; + } + + for (auto sc = model->get_schema().cbegin(); sc != model->get_schema().cend(); ++sc) + { + std::unordered_map<::odata::utility::string_t, std::shared_ptr> entity_types = (*sc)->get_entity_types(); + for (auto entity_type_iter = entity_types.cbegin(); entity_type_iter != entity_types.cend(); ++entity_type_iter) + { + resolve_type_under_structured_type(model, entity_type_iter->second); + + resovle_entity_base_type(model, entity_type_iter->second); + } + + std::unordered_map<::odata::utility::string_t, std::shared_ptr> complex_types = (*sc)->get_complex_types(); + for (auto complex_type_iter = complex_types.cbegin(); complex_type_iter != complex_types.cend(); ++complex_type_iter) + { + resolve_type_under_structured_type(model, complex_type_iter->second); + + resovle_complex_base_type(model, complex_type_iter->second); + } + + std::unordered_map<::odata::utility::string_t, std::shared_ptr> operation_types = (*sc)->get_operation_types(); + for (auto operation_type_iter = operation_types.cbegin(); operation_type_iter != operation_types.cend(); ++operation_type_iter) + { + resolve_type_under_operation_type(model, operation_type_iter->second); + } + + std::unordered_map<::odata::utility::string_t, std::shared_ptr> entity_containers = (*sc)->get_containers(); + for (auto entity_container_iter = entity_containers.cbegin(); entity_container_iter != entity_containers.cend(); ++entity_container_iter) + { + resolve_type_under_entity_container(model, entity_container_iter->second); + + resolve_navigation_path_for_non_contained_navigation(model, entity_container_iter->second); + } + } +} + + +std::shared_ptr edm_model_utility::resolve_type_from_name(const std::shared_ptr& model, ::odata::utility::string_t qualified_name) +{ + if (!model) + { + return nullptr; + } + + auto enum_type = model->find_enum_type(qualified_name); + if (enum_type) + { + return enum_type; + } + + auto complex_type = model->find_complex_type(qualified_name); + if (complex_type) + { + return complex_type; + } + + auto entity_type = model->find_entity_type(qualified_name); + if (entity_type) + { + return entity_type; + } + + auto operation_type = model->find_operation_type(qualified_name); + if (operation_type) + { + return operation_type; + } + + if (qualified_name.substr(0, 4) == U("Edm.")) + { + auto prmitive_type = edm_model_utility::get_edm_primitive_type_from_name(qualified_name); + + if (!prmitive_type) + { + return edm_named_type::EDM_UNKNOWN(); + } + + return prmitive_type; + } + else if (qualified_name.substr(0, 10) == U("Collection")) + { + int index_first = qualified_name.find_first_of('(') + 1; + int index_last = qualified_name.find_last_of(')'); + + if (index_first >= index_last) + { + auto err = std::string("Invalid collection type name : ") + ::odata::utility::conversions::to_utf8string(qualified_name); + throw std::invalid_argument(err); + } + + ::odata::utility::string_t element_name = qualified_name.substr(index_first, index_last - index_first); + + auto collection = std::make_shared(resolve_type_from_name(model, element_name)); + return collection; + } + + return nullptr; +} + +::odata::utility::string_t edm_model_utility::get_collection_element_name(const ::odata::utility::string_t& collection_full_name) +{ + ::odata::utility::string_t element_name; + + if (collection_full_name.substr(0, 10) == U("Collection")) + { + int index_first = collection_full_name.find_first_of('(') + 1; + int index_last = collection_full_name.find_last_of(')'); + + if (index_first >= index_last) + { + auto err = std::string("Invalid collection type name : ") + ::odata::utility::conversions::to_utf8string(collection_full_name); + throw std::invalid_argument(err); + } + + element_name = collection_full_name.substr(index_first, index_last - index_first); + } + + return element_name; +} + +void edm_model_utility::resolve_type_under_structured_type(const std::shared_ptr& model, const std::shared_ptr& structyred_type) +{ + if (!model) + { + return ; + } + + if (structyred_type) + { + for (auto propery_iter = structyred_type->begin(); propery_iter != structyred_type->end(); ++propery_iter) + { + auto prop = propery_iter->second; + + auto property_type = prop->get_property_type(); + if (!property_type) + { + continue; + } + + if (property_type->get_type_kind() == edm_type_kind_t::Unknown) + { + ::odata::utility::string_t type_name = property_type->get_name(); + + auto resolved_type = resolve_type_from_name(model, type_name); + if (resolved_type) + { + prop->set_property_type(resolved_type); + } + } + else if (property_type->get_type_kind() == edm_type_kind_t::Collection) + { + auto collection_type = std::dynamic_pointer_cast(property_type); + if (collection_type) + { + ::odata::utility::string_t type_name = get_collection_element_name(collection_type->get_full_name()); + + auto resolved_type = resolve_type_from_name(model, type_name); + if (resolved_type) + { + collection_type->set_element_type(resolved_type); + } + } + } + else if (property_type->get_type_kind() == edm_type_kind_t::Navigation) + { + auto navigation_type = std::dynamic_pointer_cast(property_type); + if (navigation_type) + { + auto resolved_type = resolve_type_from_name(model, navigation_type->get_name()); + if (resolved_type->get_type_kind() == edm_type_kind_t::Collection) + { + model->get_schema()[0]->m_collection_navigation_types.push_back(std::dynamic_pointer_cast(resolved_type)); + } + navigation_type->set_navigation_type(resolved_type); + } + } + } + } +} + +void edm_model_utility::resolve_type_under_operation_type(const std::shared_ptr& model, const std::shared_ptr& operationType) +{ + if (!model) + { + return ; + } + + if (operationType) + { + for (auto paramter_type_iter = operationType->get_operation_parameters().cbegin(); paramter_type_iter != operationType->get_operation_parameters().cend(); ++paramter_type_iter) + { + auto resolved_type = resolve_undetermined_type(model, (*paramter_type_iter)->get_param_type()); + if (resolved_type) + { + (*paramter_type_iter)->set_param_type(resolved_type); + } + } + + auto resolved_type = resolve_undetermined_type(model, operationType->get_operation_return_type()); + if (resolved_type) + { + operationType->set_return_type(resolved_type); + } + } +} + +std::shared_ptr edm_model_utility::resolve_undetermined_type(const std::shared_ptr& model, const std::shared_ptr& undeterminedType) +{ + if (!model) + { + return nullptr; + } + + if (undeterminedType) + { + auto resolved_type = std::make_shared(undeterminedType->get_name(), U(""), edm_type_kind_t::None); + + if (undeterminedType->get_type_kind() == edm_type_kind_t::Unknown) + { + ::odata::utility::string_t type_name = undeterminedType->get_name(); + if (type_name.substr(0, 10) == U("Collection")) + { + int index_first = type_name.find_first_of('(') + 1; + int index_last = type_name.find_last_of(')'); + + if (index_first >= index_last) + { + auto err = std::string("Invalid collection type name : ") + ::odata::utility::conversions::to_utf8string(type_name); + throw std::invalid_argument(err); + } + + ::odata::utility::string_t element_name = type_name.substr(index_first, index_last - index_first); + auto element_type = resolve_undetermined_type(model, std::make_shared(element_name, U(""), edm_type_kind_t::Unknown)); + + resolved_type = std::make_shared(element_type); + } + else + { + resolved_type = resolve_type_from_name(model, type_name); + } + } + else if (undeterminedType->get_type_kind() == edm_type_kind_t::Collection) + { + auto collection_type = std::dynamic_pointer_cast(resolved_type); + if (collection_type) + { + auto baseTypeOfColloection = collection_type->get_element_type(); + if (baseTypeOfColloection && baseTypeOfColloection->get_type_kind() == edm_type_kind_t::Unknown) + { + auto element_type = resolve_undetermined_type(model, std::make_shared(baseTypeOfColloection->get_name(), U(""), edm_type_kind_t::Unknown)); + if (element_type) + { + collection_type->set_element_type(element_type); + } + } + } + } + + return resolved_type; + } + + return nullptr; +} + +void edm_model_utility::resolve_type_under_entity_container(const std::shared_ptr& model, const std::shared_ptr& entity_container) +{ + if (!model || !entity_container) + { + return ; + } + + auto operation_imports = entity_container->get_operation_imports(); + for (auto operation_import_iter = operation_imports.cbegin(); operation_import_iter != operation_imports.cend(); ++operation_import_iter) + { + auto op = operation_import_iter->second; + if (op) + { + ::odata::utility::string_t operation_name = op->get_name(); + op->set_operation_type(model->find_operation_type(operation_name)); + } + } + + auto singletons = entity_container->get_singletons(); + for (auto singleton_iter = singletons.cbegin(); singleton_iter != singletons.cend(); ++singleton_iter) + { + auto singleton = singleton_iter->second; + if (!singleton) + { + continue; + } + + singleton->set_entity_type(model->find_entity_type(singleton->get_entity_type_name())); + } + + for (auto entity_set_iter = entity_container->begin(); entity_set_iter != entity_container->end(); ++entity_set_iter) + { + auto entity_set = entity_set_iter->second; + if (!entity_set) + { + continue; + } + + entity_set->set_entity_type(model->find_entity_type(entity_set->get_entity_type_name())); + } +} + +void edm_model_utility::resolve_navigation_path_for_non_contained_navigation(const std::shared_ptr& model, const std::shared_ptr& entity_container) +{ + if (!model || !entity_container) + { + return ; + } + + for (auto entity_set_iter = entity_container->begin(); entity_set_iter != entity_container->end(); ++entity_set_iter) + { + auto entity_set = entity_set_iter->second; + if (!entity_set) + { + continue; + } + + auto navigation_sources = entity_set->get_navigation_sources(); + for (auto navigation_source_iter = navigation_sources.begin(); navigation_source_iter != navigation_sources.end(); ++navigation_source_iter) + { + auto navigation_type = get_navigation_property_from_path(model, entity_set->get_entity_type(), navigation_source_iter->first); + if (!navigation_type) + { + continue; + } + + std::shared_ptr target = entity_container->find_entity_set(navigation_source_iter->second); + if (!target) + { + target = entity_container->find_singleton(navigation_source_iter->second); + } + navigation_type->set_binded_navigation_source(target); + } + } +} + +std::shared_ptr edm_model_utility::get_navigation_property_from_path(const std::shared_ptr& model, + const std::shared_ptr& entity_type, const ::odata::utility::string_t& navigation_path) +{ + std::shared_ptr navigation_type = nullptr; + + std::list<::odata::utility::string_t> paths; + ::odata::utility::string_t path = navigation_path; + ::odata::utility::split_string(path, U("/"), paths); + + if (paths.size() == 1) + { + // property name + if (entity_type) + { + auto navigation_property = entity_type->find_property(paths.front()); + + if (navigation_property) + { + navigation_type = std::dynamic_pointer_cast(navigation_property->get_property_type()); + } + } + } + else if (paths.size() == 2) + { + // full entity name/property name + auto entity_type_name = paths.front(); + auto property_name = paths.back(); + + if (model) + { + auto entity_type = model->find_entity_type(entity_type_name); + + if (entity_type) + { + auto navigation_property = entity_type->find_property(property_name); + + if (navigation_property) + { + navigation_type = std::dynamic_pointer_cast(navigation_property->get_property_type()); + } + } + } + } + + return navigation_type; +} + +void edm_model_utility::resovle_entity_base_type(const std::shared_ptr& model, const std::shared_ptr& entity_type) +{ + if (!model) + { + return ; + } + + if (!entity_type || entity_type->get_base_type_name().empty()) + { + return ; + } + + auto base_type = model->find_entity_type(entity_type->get_base_type_name()); + if (base_type) + { + entity_type->set_base_type(base_type); + } +} + +void edm_model_utility::resovle_complex_base_type(const std::shared_ptr& model, const std::shared_ptr& complex_type) +{ + if (!model) + { + return ; + } + + if (!complex_type || complex_type->get_base_type_name().empty()) + { + return ; + } + + auto base_type = model->find_complex_type(complex_type->get_base_type_name()); + if (base_type) + { + complex_type->set_base_type(base_type); + } +} + +std::shared_ptr edm_model_utility::get_collection_element_type(const std::shared_ptr& input_type) +{ + if (!input_type) + { + return nullptr; + } + + if (input_type->get_type_kind() != edm_type_kind_t::Collection) + { + return nullptr; + } + + auto collection_type = std::dynamic_pointer_cast(input_type); + if (!collection_type) + { + return nullptr; + } + + return collection_type->get_element_type(); +} + +std::shared_ptr edm_model_utility::get_navigation_element_type(const std::shared_ptr& input_type) +{ + if (!input_type) + { + return nullptr; + } + + if (input_type->get_type_kind() != edm_type_kind_t::Navigation) + { + return nullptr; + } + + auto navigation_type = std::dynamic_pointer_cast(input_type); + if (!navigation_type) + { + return nullptr; + } + + return navigation_type->get_navigation_type(); +} + +std::shared_ptr<::odata::edm::edm_entity_type> edm_model_utility::get_entity_type(std::shared_ptr<::odata::edm::edm_named_type> type) +{ + auto target_type = type; + + if (type->get_type_kind() == ::odata::edm::edm_type_kind_t::Collection) + { + auto collection_type = std::static_pointer_cast<::odata::edm::edm_collection_type>(type); + target_type = collection_type->get_element_type(); + } + + if (target_type->get_type_kind() == ::odata::edm::edm_type_kind_t::Entity) + { + return std::static_pointer_cast<::odata::edm::edm_entity_type>(target_type); + } + + return nullptr; +} + +std::shared_ptr<::odata::edm::edm_structured_type> edm_model_utility::get_structured_type(std::shared_ptr<::odata::edm::edm_named_type> type) +{ + auto target_type = type; + + if (type->get_type_kind() == ::odata::edm::edm_type_kind_t::Collection) + { + target_type = get_collection_element_type(type); + } + + if (target_type->get_type_kind() != ::odata::edm::Entity && target_type->get_type_kind() != ::odata::edm::Complex) + { + return nullptr; + } + + return std::static_pointer_cast<::odata::edm::edm_structured_type>(target_type); +} + +std::shared_ptr<::odata::edm::edm_named_type> edm_model_utility::get_navigation_source_type(std::shared_ptr<::odata::edm::edm_navigation_source> navigation_source) +{ + if (navigation_source->get_resource_type() == ::odata::edm::container_resource_type::E_RESOURCE_ENTITY_SET) + { + return std::static_pointer_cast<::odata::edm::edm_entity_set>(navigation_source)->get_entity_type(); + } + else + { + return std::static_pointer_cast<::odata::edm::edm_singleton>(navigation_source)->get_entity_type(); + } +} + +std::shared_ptr edm_model_utility::get_property_type_from_name(const std::shared_ptr& entity_type, const ::odata::utility::string_t& property_name) +{ + if (!entity_type) + { + return nullptr; + } + + auto entity_property = entity_type->find_property(property_name); + if (!entity_property) + { + return nullptr; + } + + auto property_type = entity_property->get_property_type(); + + if (!property_type) + { + return nullptr; + } + + if (property_type->get_type_kind() == edm_type_kind_t::Navigation) + { + property_type = get_navigation_element_type(property_type); + } + + if (property_type->get_type_kind() == edm_type_kind_t::Collection) + { + property_type = get_collection_element_type(property_type); + } + + return property_type; +} + +bool edm_model_utility::is_collection_of_entity(std::shared_ptr<::odata::edm::edm_named_type> type) +{ + if (type->get_type_kind() != ::odata::edm::edm_type_kind_t::Collection) + { + return false; + } + + auto element_type = std::static_pointer_cast<::odata::edm::edm_collection_type>(type)->get_element_type(); + if (element_type->get_type_kind() != ::odata::edm::edm_type_kind_t::Entity) + { + return false; + } + + return true; +} + +}} \ No newline at end of file diff --git a/src/edm/edm_model_writer.cpp b/src/edm/edm_model_writer.cpp new file mode 100644 index 0000000..9000d63 --- /dev/null +++ b/src/edm/edm_model_writer.cpp @@ -0,0 +1,221 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/edm/edm_model_writer.h" + +namespace odata { namespace edm +{ +void edm_model_writer::write_model(std::shared_ptr model) +{ + //write_start_element_with_prefix(U("edmx"), U("Edmx"), U("edmx")); + write_start_element(U("Edmx"), U("edmx")); + write_attribute_string(U("xmlns"), U("edmx"), U(""), U("http://docs.oasis-open.org/odata/ns/edmx")); + write_attribute_string(U(""), U("Version"), U(""), model->get_version()); + + + write_start_element(U("DataServices"), U("edmx")); + for(auto schema_iter = model->get_schema().cbegin(); schema_iter != model->get_schema().cend(); schema_iter++) + { + write_schema(*schema_iter); + } + + + write_end_element(); + write_end_element(); + + finalize(); +} + +void edm_model_writer::write_schema(std::shared_ptr schema) +{ + write_start_element(U("Schema"), U("ns")); + write_attribute_string(U("xmlns"), U("ns"), U(""), U("http://docs.oasis-open.org/odata/ns/edm")); + write_attribute_string(U(""), U("Namespace"), U(""), schema->get_name()); + + for(auto iter = schema->get_enum_types().cbegin(); iter != schema->get_enum_types().cend(); iter++) + { + write_enum_type(iter->second); + } + + for(auto iter = schema->get_complex_types().cbegin(); iter != schema->get_complex_types().cend(); iter++) + { + write_complex_type(iter->second); + } + + for(auto iter = schema->get_entity_types().cbegin(); iter != schema->get_entity_types().cend(); iter++) + { + write_entity_type(iter->second); + } + + for(auto iter = schema->get_operation_types().cbegin(); iter != schema->get_operation_types().cend(); iter++) + { + write_operation(iter->second); + } + + for(auto iter = schema->get_containers().cbegin(); iter != schema->get_containers().cend(); iter++) + { + write_entity_container(iter->second); + } + + write_end_element(); +} + +void edm_model_writer::write_enum_type(std::shared_ptr enum_type) +{ + write_start_element(U("EnumType")); + write_attribute_string(U(""), U("Name"), U(""), enum_type->get_name()); + + for(auto iter = enum_type->get_enum_members().cbegin(); iter != enum_type->get_enum_members().cend(); iter++) + { + write_start_element(U("Memeber")); + write_attribute_string(U(""), U("Name"), U(""), (*iter) -> get_enum_member_name()); + + ::odata::utility::string_t str; + ::odata::utility::stringstream_t ss; + ss << (*iter) -> get_enum_member_value(); + ss >> str; + write_attribute_string(U(""), U("Value"), U(""), str); + write_end_element(); + } + + write_end_element(); +} + +void edm_model_writer::write_complex_type(std::shared_ptr complex_type) +{ + write_start_element(U("ComplexType")); + write_attribute_string(U(""), U("Name"), U(""), complex_type->get_name()); + + for(auto iter = complex_type->begin(); iter != complex_type->end(); iter++) + { + write_start_element(U("Property")); + write_attribute_string(U(""), U("Name"), U(""), iter->second->get_name()); + write_attribute_string(U(""), U("Type"), U(""), iter->second->get_property_type()->get_name()); + write_end_element(); + } + + write_end_element(); +} + +void edm_model_writer::write_entity_type(std::shared_ptr entity_type) +{ + write_start_element(U("EntityType")); + write_attribute_string(U(""), U("Name"), U(""), entity_type->get_name()); + + write_start_element(U("Key")); + + for(auto iter = entity_type->key().cbegin(); iter != entity_type->key().cend(); iter++) + { + write_start_element(U("PropertyRef")); + write_attribute_string(U(""), U("Name"), U(""), *iter); + write_end_element(); + } + + write_end_element(); + + for(auto iter = entity_type->begin(); iter != entity_type->end(); iter++) + { + write_start_element(U("Property")); + write_attribute_string(U(""), U("Name"), U(""), iter->second->get_name()); + write_attribute_string(U(""), U("Type"), U(""), iter->second->get_property_type()->get_name()); + write_end_element(); + } + + write_end_element(); +} + +void edm_model_writer::write_operation(std::shared_ptr operation) +{ + write_start_element(operation->is_function() ? U("Function") : U("Action")); + write_attribute_string(U(""), U("Name"), U(""), operation->get_name()); + write_attribute_string(U(""), U("IsBound"), U(""), operation->is_bound() ? U("true") : U("false")); + write_attribute_string(U(""), U("IsComposable"), U(""), operation->is_composable() ? U("true") : U("false")); + + for(auto iter = operation->get_operation_parameters().cbegin(); iter != operation->get_operation_parameters().cend(); iter++) + { + write_start_element(U("Parameter")); + write_attribute_string(U(""), U("Name"), U(""), (*iter)->get_param_name()); + write_attribute_string(U(""), U("Type"), U(""), (*iter)->get_param_type()->get_name()); + write_end_element(); + } + + if (operation->get_operation_return_type()) + { + write_start_element(U("ReturnType")); + write_attribute_string(U(""), U("Type"), U(""), operation->get_operation_return_type()->get_name()); + write_end_element(); + } + + write_end_element(); +} + +void edm_model_writer::write_entity_container(std::shared_ptr entity_container) +{ + write_start_element(U("EntityContainer")); + write_attribute_string(U(""), U("Name"), U(""), entity_container->get_name()); + + for(auto iter = entity_container->begin(); iter != entity_container->end(); iter++) + { + write_entity_set(iter->second); + } + + for(auto iter = entity_container->get_singletons().cbegin(); iter != entity_container->get_singletons().cend(); iter++) + { + write_singleton(iter->second); + } + + for(auto iter = entity_container->get_operation_imports().cbegin(); iter != entity_container->get_operation_imports().cend(); iter++) + { + write_operation_import(iter->second); + } + + write_end_element(); +} + +void edm_model_writer::write_entity_set(std::shared_ptr entity_set) +{ + write_start_element(U("EntitySet")); + write_attribute_string(U(""), U("Name"), U(""), entity_set->get_name()); + write_attribute_string(U(""), U("EntityType"), U(""), entity_set->get_entity_type_name()); + + for(auto iter = entity_set->get_navigation_sources().cbegin(); iter != entity_set->get_navigation_sources().cend(); iter++) + { + write_start_element(U("NavigationPropertyBinding")); + write_attribute_string(U(""), U("Path"), U(""), iter->first); + write_attribute_string(U(""), U("Target"), U(""), iter->second); + write_end_element(); + } + + write_end_element(); +} + +void edm_model_writer::write_singleton(std::shared_ptr singleton) +{ + write_start_element(U("Singleton")); + write_attribute_string(U(""), U("Name"), U(""), singleton->get_name()); + write_attribute_string(U(""), U("EntityType"), U(""), singleton->get_entity_type_name()); + + for(auto iter = singleton->get_navigation_sources().cbegin(); iter != singleton->get_navigation_sources().cend(); iter++) + { + write_start_element(U("NavigationPropertyBinding")); + write_attribute_string(U(""), U("Path"), U(""), iter->first); + write_attribute_string(U(""), U("Target"), U(""), iter->second); + write_end_element(); + } + + write_end_element(); +} + +void edm_model_writer::write_operation_import(std::shared_ptr operation_import) +{ + write_start_element(operation_import->get_operation_import_kind() == OperationImportKind::FunctionImport ? U("FunctionImport") : U("ActionImport")); + write_attribute_string(U(""), U("Name"), U(""), operation_import->get_name()); + write_attribute_string(U(""), U("EntitySet"), U(""), operation_import->get_entity_set_name()); + + write_end_element(); +} + +}} diff --git a/src/edm/edm_schema.cpp b/src/edm/edm_schema.cpp new file mode 100644 index 0000000..06bac6f --- /dev/null +++ b/src/edm/edm_schema.cpp @@ -0,0 +1,84 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/edm/edm_schema.h" + +namespace odata { namespace edm +{ + +std::shared_ptr edm_schema::find_entity_type(::odata::utility::string_t name) const +{ + auto nsp_size = m_namespace.size(); + if (name.size() > nsp_size && name.substr(0, nsp_size) == m_namespace) + { + // Strip qualification when searching. + name = name.substr(nsp_size + 1); + } + + auto find_iter = m_entity_types.find(name); + if (find_iter != m_entity_types.end()) + { + return find_iter->second; + } + + return nullptr; +} + +std::shared_ptr edm_schema::find_complex_type(::odata::utility::string_t name) const +{ + auto nsp_size = m_namespace.size(); + if (name.size() > nsp_size && name.substr(0, nsp_size) == m_namespace) + { + // Strip qualification when searching. + name = name.substr(nsp_size + 1); + } + + auto find_iter = m_complex_types.find(name); + if (find_iter != m_complex_types.end()) + { + return find_iter->second; + } + + return nullptr; +} + +std::shared_ptr edm_schema::find_enum_type(::odata::utility::string_t name) const +{ + auto nsp_size = m_namespace.size(); + if ( name.size() > nsp_size && name.substr(0, nsp_size) == m_namespace) + { + // Strip qualification when searching. + name = name.substr(nsp_size + 1); + } + + auto find_iter = m_enum_types.find(name); + if (find_iter != m_enum_types.end()) + { + return find_iter->second; + } + + return nullptr; +} + +std::shared_ptr edm_schema::find_operation_type(::odata::utility::string_t name) const +{ + auto nsp_size = m_namespace.size(); + if ( name.size() > nsp_size && name.substr(0, nsp_size) == m_namespace) + { + // Strip qualification when searching. + name = name.substr(nsp_size+1); + } + + auto find_iter = m_operation_types.find(name); + if (find_iter != m_operation_types.end()) + { + return find_iter->second; + } + + return nullptr; +} + +}} \ No newline at end of file diff --git a/src/edm/edm_type.cpp b/src/edm/edm_type.cpp new file mode 100644 index 0000000..4c7e398 --- /dev/null +++ b/src/edm/edm_type.cpp @@ -0,0 +1,265 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata/edm/odata_edm.h" + +namespace odata { namespace edm +{ + + +std::shared_ptr edm_named_type::_EDM_UNKNOWN = nullptr; + +std::shared_ptr edm_named_type::EDM_UNKNOWN() +{ + std::lock_guard lock(mutex()); + + if (!_EDM_UNKNOWN) + { + _EDM_UNKNOWN.reset(new edm_named_type(edm_type_kind_t::Unknown)); + } + + return _EDM_UNKNOWN; +} + +std::mutex &edm_named_type::mutex() +{ + static std::mutex _mutex; + return _mutex; +} + +std::shared_ptr edm_primitive_type::_BINARY = nullptr; +std::shared_ptr edm_primitive_type::_BOOLEAN = nullptr; +std::shared_ptr edm_primitive_type::_BYTE = nullptr; +std::shared_ptr edm_primitive_type::_DATETIMEOFFSET = nullptr; +std::shared_ptr edm_primitive_type::_DURATION = nullptr; +std::shared_ptr edm_primitive_type::_DECIMAL = nullptr; +std::shared_ptr edm_primitive_type::_DOUBLE = nullptr; +std::shared_ptr edm_primitive_type::_GUID = nullptr; +std::shared_ptr edm_primitive_type::_INT16 = nullptr; +std::shared_ptr edm_primitive_type::_INT32 = nullptr; +std::shared_ptr edm_primitive_type::_INT64 = nullptr; +std::shared_ptr edm_primitive_type::_SBYTE = nullptr; +std::shared_ptr edm_primitive_type::_SINGLE = nullptr; +std::shared_ptr edm_primitive_type::_STRING = nullptr; +std::shared_ptr edm_primitive_type::_STREAM = nullptr; +std::shared_ptredm_primitive_type:: _UNKNOWN = nullptr; + +std::shared_ptr edm_primitive_type::BINARY() +{ + std::lock_guard lock(mutex()); + + if (!_BINARY) + { + _BINARY.reset(new edm_primitive_type(U("Edm.Binary"), edm_primitive_type_kind_t::Binary)); + } + + return _BINARY; +} + +std::shared_ptr edm_primitive_type::BOOLEAN() +{ + std::lock_guard lock(mutex()); + + if (!_BOOLEAN) + { + _BOOLEAN.reset(new edm_primitive_type(U("Edm.Boolean"), edm_primitive_type_kind_t::Boolean)); + } + + return _BOOLEAN; +} + +std::shared_ptr edm_primitive_type::BYTE() +{ + std::lock_guard lock(mutex()); + + if (!_BYTE) + { + _BYTE.reset(new edm_primitive_type(U("Edm.Byte"), edm_primitive_type_kind_t::Byte)); + } + + return _BYTE; +} + +std::shared_ptr edm_primitive_type::DATETIMEOFFSET() +{ + std::lock_guard lock(mutex()); + + if (!_DATETIMEOFFSET) + { + _DATETIMEOFFSET.reset(new edm_primitive_type(U("Edm.DateTimeOffset"), edm_primitive_type_kind_t::DateTimeOffset)); + } + + return _DATETIMEOFFSET; +} + +std::shared_ptr edm_primitive_type::DURATION() +{ + std::lock_guard lock(mutex()); + + if (!_DURATION) + { + _DURATION.reset(new edm_primitive_type(U("Edm.Duration"), edm_primitive_type_kind_t::Duration)); + } + + return _DURATION; +} + +std::shared_ptr edm_primitive_type::DECIMAL() +{ + std::lock_guard lock(mutex()); + + if (!_DECIMAL) + { + _DECIMAL.reset(new edm_primitive_type(U("Edm.Decimal"), edm_primitive_type_kind_t::Decimal)); + } + + return _DECIMAL; +} + +std::shared_ptr edm_primitive_type::DOUBLE() +{ + std::lock_guard lock(mutex()); + + if (!_DOUBLE) + { + _DOUBLE.reset(new edm_primitive_type(U("Edm.Double"), edm_primitive_type_kind_t::Double)); + } + + return _DOUBLE; +} + +std::shared_ptr edm_primitive_type::GUID() +{ + std::lock_guard lock(mutex()); + + if (!_GUID) + { + _GUID.reset(new edm_primitive_type(U("Edm.Guid"), edm_primitive_type_kind_t::Guid)); + } + + return _GUID; +} + +std::shared_ptr edm_primitive_type::INT16() +{ + std::lock_guard lock(mutex()); + + if (!_INT16) + { + _INT16.reset(new edm_primitive_type(U("Edm.Int16"), edm_primitive_type_kind_t::Int16)); + } + + return _INT16; +} + +std::shared_ptr edm_primitive_type::INT32() +{ + std::lock_guard lock(mutex()); + + if (!_INT32) + { + _INT32.reset(new edm_primitive_type(U("Edm.Int32"), edm_primitive_type_kind_t::Int32)); + } + + return _INT32; +} + +std::shared_ptr edm_primitive_type::INT64() +{ + std::lock_guard lock(mutex()); + + if (!_INT64) + { + _INT64.reset(new edm_primitive_type(U("Edm.Int64"), edm_primitive_type_kind_t::Int64)); + } + + return _INT64; +} + +std::shared_ptr edm_primitive_type::SBYTE() +{ + std::lock_guard lock(mutex()); + + if (!_SBYTE) + { + _SBYTE.reset(new edm_primitive_type(U("Edm.SByte"), edm_primitive_type_kind_t::SByte)); + } + + return _SBYTE; +} + +std::shared_ptr edm_primitive_type::SINGLE() +{ + std::lock_guard lock(mutex()); + + if (!_SINGLE) + { + _SINGLE.reset(new edm_primitive_type(U("Edm.Single"), edm_primitive_type_kind_t::Single)); + } + + return _SINGLE; +} + +std::shared_ptr edm_primitive_type::STRING() +{ + std::lock_guard lock(mutex()); + + if (!_STRING) + { + _STRING.reset(new edm_primitive_type(U("Edm.String"), edm_primitive_type_kind_t::String)); + } + + return _STRING; +} + +std::shared_ptr edm_primitive_type::STREAM() +{ + std::lock_guard lock(mutex()); + + if (!_STREAM) + { + _STREAM.reset(new edm_primitive_type(U("Edm.Stream"), edm_primitive_type_kind_t::Stream)); + } + + return _STREAM; +} + +std::shared_ptr edm_primitive_type::UNKNOWN() +{ + std::lock_guard lock(mutex()); + + if (!_UNKNOWN) + { + _UNKNOWN.reset(new edm_primitive_type(U("UNKNOWN"), edm_primitive_type_kind_t::NoneVal)); + } + + return _UNKNOWN; +} + +std::mutex &edm_primitive_type::mutex() +{ + static std::mutex _mutex; + return _mutex; +} + +std::shared_ptr edm_structured_type::find_property(::odata::utility::string_t name) const +{ + // todo find property type in base type... + auto find_iter = m_properties.find(name); + if (find_iter != m_properties.end()) + { + return find_iter->second; + } + else if (m_base_type) + { + // find property type in base type + return m_base_type->find_property(name); + } + + return nullptr; +} + +}} \ No newline at end of file diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 0000000..67ed599 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,6 @@ +set(ODATACPP_TEST_FRAMEWORK_DIR ${ODATACPP_TEST_DIR}/framework) +set(UnitTestpp_INCLUDE_DIR ${ODATACPP_TEST_FRAMEWORK_DIR}/UnitTestpp) +set(Utilities_INCLUDE_DIR ${ODATACPP_TEST_FRAMEWORK_DIR}/utilities/include) + +add_subdirectory(framework) +add_subdirectory(functional) \ No newline at end of file diff --git a/tests/e2e_service/create_handler.cpp b/tests/e2e_service/create_handler.cpp new file mode 100644 index 0000000..6084441 --- /dev/null +++ b/tests/e2e_service/create_handler.cpp @@ -0,0 +1,59 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "cpprest/asyncrt_utils.h" +#include "odata_value_builder.h" +#include "odata/core/odata_message_writer.h" +#include "odata/core/odata_message_reader.h" +#include "data_source_task.h" +#include "odata_service_exception.h" +#include "create_handler.h" + + +using namespace ::std; +using namespace ::web::http; +using namespace ::odata::edm; + +namespace odata { namespace service +{ + create_handler::create_handler(shared_ptr request, shared_ptr model): m_request(request), m_model(model) + { + m_request_context = make_shared(m_request, m_model); + } + + void create_handler::handle() + { + auto target = m_request_context->get_query_target(); + + auto target_edm_type = target->target_edm_type(); + + if (!target_edm_type || target_edm_type->get_type_kind() != edm_type_kind_t::Collection) + { + throw new odata_service_exception(U("Only creating an entity in entity set is supported.")); + } + + auto entity_collection_type = std::dynamic_pointer_cast(target_edm_type); + auto entity_type = std::dynamic_pointer_cast(entity_collection_type->get_element_type()); + if (!entity_type) + { + throw new odata_service_exception(U("Only creating an entity in entity set is supported.")); + } + + ::odata::core::odata_message_reader reader(m_model, U("http://localhost:4789"), m_request_context->get_request_body(), false); + auto entity_value = reader.read_entity_value(entity_type); + + shared_ptr ds_task = make_shared(m_request_context, entity_value); + const std::vector<::mongo::BSONObj> &result = ds_task->run(); + + auto value_builder = make_shared(); + auto target_value = value_builder->build_odata_value(target->target_edm_type(), result); + + ::odata::core::odata_message_writer writer(m_model, U("http://localhost:4789")); + auto content = writer.write_odata_value(target_value); + + m_request->reply(status_codes::Created, content).get(); + } +}} \ No newline at end of file diff --git a/tests/e2e_service/create_handler.h b/tests/e2e_service/create_handler.h new file mode 100644 index 0000000..1478651 --- /dev/null +++ b/tests/e2e_service/create_handler.h @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "cpprest/http_msg.h" +#include "odata/edm/edm_model.h" +#include "odata_request_context.h" + +namespace odata { namespace service +{ + +class create_handler +{ +public: + create_handler(::std::shared_ptr<::web::http::http_request> request, ::std::shared_ptr<::odata::edm::edm_model> model); + ~create_handler() {}; + + void handle(); + +private: + ::std::shared_ptr<::web::http::http_request> m_request; + ::std::shared_ptr<::odata::edm::edm_model> m_model; + ::std::shared_ptr m_request_context; +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/data_source_task.cpp b/tests/e2e_service/data_source_task.cpp new file mode 100644 index 0000000..6c619d0 --- /dev/null +++ b/tests/e2e_service/data_source_task.cpp @@ -0,0 +1,327 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include + +#include "data_source_task.h" +#include "query_node_visitor.h" +#include "odata_value_builder.h" +#include "odata_service_exception.h" + +using namespace ::std; +using namespace ::utility; +using namespace ::odata::edm; +using namespace ::odata::core; + +namespace odata { namespace service +{ + mongo_task::mongo_task(data_source_operation_type operation_type, shared_ptr request_context) : m_operation_type(operation_type), m_request_context(request_context) + { + + } + + const std::vector<::mongo::BSONObj> &mongo_task::run() + { + resolve_request(); + + if (m_actions.size() == 0) + { + throw new odata_service_exception(U("Invalid request. No actions can be done.")); + } + else + { + return m_actions[m_actions.size() - 1]->execute(); + } + } + + mongo_query_task::mongo_query_task(shared_ptr request_context) : mongo_task(ds_query, request_context) + { + + } + + void mongo_query_task::resolve_request() + { + auto path_segments = m_request_context->get_odata_path()->segments(); + string db_name("trippin"); + + shared_ptr last_segment; + for (auto iter = path_segments.cbegin(); iter != path_segments.cend(); iter++) + { + shared_ptr seg = *iter; + switch (seg->segment_type()) + { + case odata_path_segment_type::EntitySet: + { + if (last_segment != nullptr) + { + //throw exception + } + + shared_ptr entityset_seg = seg->as(); + string_t entityset_name = entityset_seg->entity_set()->get_name(); + string entityset_name_s(entityset_name.cbegin(), entityset_name.cend()); + + shared_ptr start_action = make_shared(ds_query, db_name, entityset_name_s); + start_action->projection_builder() << "_id" << 0; + m_actions.push_back(start_action); + break; + } + case odata_path_segment_type::Key: + { + int last_segment_type = last_segment->segment_type(); + if (last_segment_type != odata_path_segment_type::EntitySet) + { + //throw exception + } + + shared_ptr key_seg = seg->as(); + auto keys = key_seg->keys(); + + for (auto key = keys.cbegin(); key != keys.cend(); key++) + { + string key_name_s, key_value_s; + key_name_s.assign(key->first.cbegin(), key->first.cend()); + string_t second = key->second->as<::odata::utility::string_t>(); + key_value_s.assign(second.cbegin(), second.cend()); + m_actions[m_actions.size()-1]->query_builder() << key_name_s << key_value_s; + } + break; + } + case odata_path_segment_type::StructuralProperty: + { + int last_segment_type = last_segment->segment_type(); + if (last_segment_type != odata_path_segment_type::Key) + { + //throw exception + } + + shared_ptr property_seg = seg->as(); + string_t property_name = property_seg->property()->get_name(); + + string property_name_s(property_name.cbegin(), property_name.cend()); + m_actions[m_actions.size()-1]->projection_builder() << property_name_s << 1; + + break; + } + + case odata_path_segment_type::NavigationProperty: + { + int last_segment_type = last_segment->segment_type(); + if (last_segment_type != odata_path_segment_type::Key) + { + //throw exception + } + + shared_ptr property_seg = seg->as(); + string_t property_name = property_seg->property()->get_name(); + shared_ptr target_source = property_seg->navigation_type()->get_binded_navigation_source(); + string_t target_source_name = target_source->get_name(); + + string property_name_s(property_name.cbegin(), property_name.cend()); + string target_source_name_s(target_source_name.cbegin(), target_source_name.cend()); + + m_actions[m_actions.size()-1]->projection_builder() << property_name_s << 1; + shared_ptr another_action = make_shared(ds_query, db_name, target_source_name_s, m_actions[m_actions.size()-1], property_name_s); + another_action->projection_builder() << "_id" << 0; + m_actions.push_back(another_action); + break; + } + default: + { + break; + } + } + + last_segment = seg; + + } + + auto filter_clause = m_request_context->get_filter_clause(); + if (filter_clause) + { + auto query_node_visitor = make_shared(); + ::mongo::BSONObj filter_query_obj = query_node_visitor->translate_node(filter_clause->expression()); + + ::mongo::BSONObjIterator fieldIterator(filter_query_obj["query"].Obj()); + while (fieldIterator.more()) + { + m_actions[m_actions.size()-1]->query_builder() << fieldIterator.next(); + } + } + + auto orderby_clause = m_request_context->get_orderby_clause(); + if (orderby_clause) + { + auto query_node_visitor = make_shared(); + for (auto iter = orderby_clause->items().cbegin(); iter!= orderby_clause->items().cend(); iter++) + { + ::mongo::BSONObj orderby_obj = query_node_visitor->translate_node(iter->first); + m_actions[m_actions.size()-1]->orderby_builder() << orderby_obj["query"].String() << (iter->second ? 1 : -1); + + } + + } + } + + + + mongo_insert_task::mongo_insert_task(shared_ptr request_context, std::shared_ptr<::odata::core::odata_value> insert_value) + : mongo_task(ds_insert, request_context), m_insert_value(insert_value) + { + + } + + void mongo_insert_task::resolve_request() + { + auto path_segments = m_request_context->get_odata_path()->segments(); + string db_name("trippin"); + + if (path_segments.size() != 1) + { + //throw exception to simplify the scenario for now. should be removed later. + } + + auto value_builder = std::make_shared(); + auto insert_document = value_builder->build_BSONObj(m_insert_value); + + shared_ptr last_segment; + for (auto iter = path_segments.cbegin(); iter != path_segments.cend(); iter++) + { + shared_ptr seg = *iter; + switch (seg->segment_type()) + { + case odata_path_segment_type::EntitySet: + { + if (last_segment != nullptr) + { + //throw exception + } + + shared_ptr entityset_seg = seg->as(); + string_t entityset_name = entityset_seg->entity_set()->get_name(); + string entityset_name_s(entityset_name.cbegin(), entityset_name.cend()); + + shared_ptr start_action = make_shared(ds_insert, db_name, entityset_name_s); + start_action->set_input_documents(insert_document); + m_actions.push_back(start_action); + break; + } + default: + { + break; + } + } + + last_segment = seg; + + } + + if (last_segment->segment_type() == odata_path_segment_type::EntitySet) + { + shared_ptr entityset_seg = last_segment->as(); + string_t entityset_name = entityset_seg->entity_set()->get_name(); + string entityset_name_s(entityset_name.cbegin(), entityset_name.cend()); + + shared_ptr query_after_insert = make_shared(ds_query, db_name, entityset_name_s, m_actions[m_actions.size()-1], ""); + + auto entity_key = entityset_seg->entity_type()->key(); + for (auto key = entity_key.cbegin(); key != entity_key.cend(); key++) + { + string key_name_s; + key_name_s.assign(key->cbegin(), key->cend()); + + auto target_key_value_pair = insert_document.getField(key_name_s); + query_after_insert->query_builder() << target_key_value_pair; + } + + m_actions.push_back(query_after_insert); + } + else { + //throw exceptions here. + } + } + + mongo_initialize_task::mongo_initialize_task() : mongo_task(ds_insert, nullptr) + { + + } + + void mongo_initialize_task::resolve_request() + { + string db_name("trippin"), people("People"), airports("Airports"); + + vector airports_data; + mongo::OID aid_1 = mongo::OID::gen(); + airports_data.push_back(BSON("_id" << aid_1 << "IcaoCode" << "KSFO" << "Name" << "San Francisco International Airport" << "IataCode" << "SFO" + << "Location" << BSON("Address" << "South McDonnell Road, San Francisco, CA 94128" + << "City" << BSON("CountryRegion" << "United States" << "Name" << "San Francisco" << "Region" << "California")) + )); + mongo::OID aid_2 = mongo::OID::gen(); + airports_data.push_back(BSON("_id" << aid_2 << "IcaoCode" << "KLAX" << "Name" << "Los Angeles International Airport" << "IataCode" << "LAX" + << "Location" << BSON("Address" << "1 World Way, Los Angeles, CA, 90045" + << "City" << BSON("CountryRegion" << "United States" << "Name" << "Los Angeles" << "Region" << "California")) + )); + mongo::OID aid_3 = mongo::OID::gen(); + airports_data.push_back(BSON("_id" << aid_3 << "IcaoCode" << "ZSSS" << "Name" << "Shanghai Hongqiao International Airport" << "IataCode" << "SHA" + << "Location" << BSON("Address" << "Hongqiao Road 2550, Changning District" + << "City" << BSON("CountryRegion" << "China" << "Name" << "Shanghai" << "Region" << "Shanghai")) + )); + mongo::OID aid_4 = mongo::OID::gen(); + airports_data.push_back(BSON("_id" << aid_4 << "IcaoCode" << "ZBAA" << "Name" << "Beijing Capital International Airport" << "IataCode" << "PEK" + << "Location" << BSON("Address" << "Airport Road, Chaoyang District, Beijing, 100621" + << "City" << BSON("CountryRegion" << "China" << "Name" << "Beijing" << "Region" << "Beijing")) + )); + + + vector people_data; + mongo::OID pid_1 = mongo::OID::gen(); + people_data.push_back(BSON("_id" << pid_1 << "UserName" << "russellwhyte" << "FirstName" << "Russell" << "LastName" << "Whyte" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Russell@example.com" << "Russell@contoso.com") + << "AddressInfo" << BSON_ARRAY(BSON("Address" << "187 Suffolk Ln." << "City" << BSON("CountryRegion" << "United States" << "Name" << "Boise" << "Region" << "ID"))) + //<< "RecentAirports" << BSON_ARRAY("KSFO" << "KLAX") + << "RecentAirports" << BSON_ARRAY(aid_1 << aid_2) + )); + mongo::OID pid_2 = mongo::OID::gen(); + people_data.push_back(BSON("_id" << pid_2 << "UserName" << "scottketchum" << "FirstName" << "Scott" << "LastName" << "Ketchum" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Scott@example.com") + << "AddressInfo" << BSON_ARRAY(BSON("Address" << "2817 Milton Dr." << "City" << BSON("CountryRegion" << "United States" << "Name" << "Albuquerque" << "Region" << "NM"))) + //<< "RecentAirports" << BSON_ARRAY("KLAX") + << "RecentAirports" << BSON_ARRAY(aid_2) + )); + mongo::OID pid_3 = mongo::OID::gen(); + people_data.push_back(BSON("_id" << pid_3 << "UserName" << "ronaldmundy" << "FirstName" << "Ronald" << "LastName" << "Mundy" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Ronald@example.com" << "Ronald@contoso.com") + << "AddressInfo" << mongo::BSONArrayBuilder().arr() + //<< "RecentAirports" << BSON_ARRAY("ZSSS" << "ZBAA") + << "RecentAirports" << BSON_ARRAY(aid_1 << aid_3 << aid_4) + )); + mongo::OID pid_4 = mongo::OID::gen(); + people_data.push_back(BSON("_id" << pid_4 << "UserName" << "javieralfred" << "FirstName" << "Javier" << "LastName" << "Alfred" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Javier@example.com" << "Javier@contoso.com") + << "AddressInfo" << BSON_ARRAY(BSON("Address" << "89 Jefferson Way Suite 2" << "City" << BSON("CountryRegion" << "United States" << "Name" << "Portland" << "Region" << "WA"))) + << "RecentAirports" << mongo::BSONArrayBuilder().arr() + )); + mongo::OID pid_5 = mongo::OID::gen(); + people_data.push_back(BSON("_id" << pid_5 << "UserName" << "willieashmore" << "FirstName" << "Willie" << "LastName" << "Ashmore" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Willie@example.com" << "Willie@contoso.com") + << "AddressInfo" << mongo::BSONArrayBuilder().arr() + << "RecentAirports" << mongo::BSONArrayBuilder().arr() + )); + + shared_ptr db_drop_action = make_shared(ds_drop, db_name, ""); + + shared_ptr people_initialize_action = make_shared(ds_insert, db_name, people, db_drop_action, ""); + people_initialize_action->set_input_documents(people_data); + + shared_ptr airports_initialize_action = make_shared(ds_insert, db_name, airports, people_initialize_action, ""); + airports_initialize_action->set_input_documents(airports_data); + + m_actions.push_back(db_drop_action); + m_actions.push_back(people_initialize_action); + m_actions.push_back(airports_initialize_action); + } + +}} \ No newline at end of file diff --git a/tests/e2e_service/data_source_task.h b/tests/e2e_service/data_source_task.h new file mode 100644 index 0000000..d9e46c4 --- /dev/null +++ b/tests/e2e_service/data_source_task.h @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include +#include + +#include +#include + +#include "mongo_action.h" +#include "odata_request_context.h" + +namespace odata { namespace service +{ + +class mongo_task +{ +public: + mongo_task(data_source_operation_type operation_type, ::std::shared_ptr request_context); + ~mongo_task() {}; + virtual const std::vector<::mongo::BSONObj> &run(); + +protected: + virtual void resolve_request() = 0; + + data_source_operation_type m_operation_type; + ::std::shared_ptr m_request_context; + ::std::vector<::std::shared_ptr> m_actions; +}; + +class mongo_query_task : public mongo_task +{ +public: + mongo_query_task(::std::shared_ptr request_context); + ~mongo_query_task() {}; + +private: + virtual void resolve_request(); +}; + +class mongo_insert_task : public mongo_task +{ +public: + mongo_insert_task(::std::shared_ptr request_context, std::shared_ptr<::odata::core::odata_value> insert_value); + ~mongo_insert_task() {}; + +private: + std::shared_ptr<::odata::core::odata_value> m_insert_value; + + virtual void resolve_request(); +}; + +class mongo_initialize_task : public mongo_task +{ +public: + mongo_initialize_task(); + ~mongo_initialize_task() {}; + +private: + virtual void resolve_request(); +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/mongo_action.cpp b/tests/e2e_service/mongo_action.cpp new file mode 100644 index 0000000..dea0389 --- /dev/null +++ b/tests/e2e_service/mongo_action.cpp @@ -0,0 +1,124 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "mongo_action.h" + +using namespace ::std; +using namespace mongo; + +namespace odata { namespace service +{ + mongo_action::mongo_action(data_source_operation_type operation_type, const string& db_name, const string& collection_name) + :m_operation_type(operation_type), m_db_name(db_name), m_collection_name(collection_name), m_finished(false) + { + + } + + mongo_action::mongo_action(data_source_operation_type operation_type, const string& db_name, const string& collection_name, ::std::shared_ptr dependency, ::std::string dependency_field) + :m_operation_type(operation_type), m_db_name(db_name), m_collection_name(collection_name), m_finished(false), m_dependency(dependency), m_dependency_field(dependency_field) + { + + } + + BSONObjBuilder &mongo_action::query_builder() + { + return m_query_builder; + } + + BSONObjBuilder &mongo_action::orderby_builder() + { + return m_orderby_builder; + } + + BSONObjBuilder &mongo_action::projection_builder() + { + return m_projection_builder; + } + + void mongo_action::set_input_documents(BSONObj document) + { + m_input_documents.clear(); + m_input_documents.push_back(document); + } + + void mongo_action::set_input_documents(const vector documents) + { + m_input_documents.clear(); + for (auto iter = documents.cbegin(); iter != documents.cend(); iter++) + { + m_input_documents.push_back(*iter); + } + } + + const vector& mongo_action::execute() + { + if (m_finished) + { + return m_result; + } + + if (m_dependency) + { + const vector& dependency_result = m_dependency->execute(); + + //If m_dependency_field is empty, that means the current action depends on the result of m_dependency, it just need the m_dependency action to finish first. + if (!m_dependency_field.empty()) + { + if (dependency_result.size() != 1) + { + //throw exception + } + auto field_value = dependency_result[0].getField(m_dependency_field); + m_query_builder << "_id" << BSON("$in" << field_value); + } + + } + + string full_collection_name = get_full_collection_name(); + + DBClientConnection c; + c.connect("localhost"); + + //TODO: handle exceptions here + cout << m_query_builder.done() << endl; + cout << m_projection_builder.done() << endl; + if (m_operation_type == ds_drop) + { + if (m_collection_name.empty()) + { + //drop the whole database + c.dropDatabase(m_db_name); + } + else + { + //drop the collection + c.dropCollection(full_collection_name); + } + } + else if (m_operation_type == ds_insert) + { + c.insert(full_collection_name, m_input_documents); + } + else if (m_operation_type == ds_query) + { + m_result_cursor = c.query(full_collection_name, Query(m_query_builder.done()).sort(m_orderby_builder.done()), 0, 0, &m_projection_builder.done()); + + while (m_result_cursor->more()) + { + m_result.push_back(m_result_cursor->next()); + } + } + + m_finished = true; + return m_result; + } + + string mongo_action::get_full_collection_name() const + { + return m_db_name + "." + m_collection_name; + } + +}} \ No newline at end of file diff --git a/tests/e2e_service/mongo_action.h b/tests/e2e_service/mongo_action.h new file mode 100644 index 0000000..fb02817 --- /dev/null +++ b/tests/e2e_service/mongo_action.h @@ -0,0 +1,70 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +#include +#include + +namespace odata { namespace service +{ + +enum data_source_operation_type +{ + ds_insert, + ds_update, + ds_query, + ds_delete, + ds_drop +}; + +class mongo_action +{ +public: + mongo_action(data_source_operation_type operation_type, const ::std::string& db_name, const ::std::string& collection_name); + + mongo_action(data_source_operation_type operation_type, const ::std::string& db_name, const ::std::string& collection_name, ::std::shared_ptr dependency, ::std::string dependency_field); + + const ::std::vector& execute(); + + ::std::string get_full_collection_name() const; + + mongo::BSONObjBuilder &query_builder(); + + mongo::BSONObjBuilder &orderby_builder(); + + mongo::BSONObjBuilder &projection_builder(); + + void set_input_documents(mongo::BSONObj document); + + void set_input_documents(const ::std::vector documents); + +private: + bool m_finished; + + data_source_operation_type m_operation_type; + + ::std::string m_db_name; + ::std::string m_collection_name; + + ::std::string m_dependency_field; + ::std::shared_ptr m_dependency; + + mongo::BSONObjBuilder m_query_builder; + mongo::BSONObjBuilder m_orderby_builder; + mongo::BSONObjBuilder m_projection_builder; + + ::std::auto_ptr m_result_cursor; + ::std::vector m_result; + + ::std::vector m_input_documents; +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_request_context.cpp b/tests/e2e_service/odata_request_context.cpp new file mode 100644 index 0000000..898cafe --- /dev/null +++ b/tests/e2e_service/odata_request_context.cpp @@ -0,0 +1,28 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata_request_context.h" + +using namespace ::std; +using namespace ::web::http; +using namespace ::odata::edm; +using namespace ::odata::core; + +namespace odata { namespace service +{ + odata_request_context::odata_request_context(shared_ptr request, shared_ptr model) : m_request(request), m_model(model) + { + m_uri_parser = make_shared(m_model); + + //TODO: handle exceptions here + m_odata_uri = m_uri_parser->parse_uri(m_request->relative_uri()); + + //TODO: temporary implementation here. + m_request_body = m_request->extract_json().get().serialize(); + + } + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_request_context.h b/tests/e2e_service/odata_request_context.h new file mode 100644 index 0000000..b7d3216 --- /dev/null +++ b/tests/e2e_service/odata_request_context.h @@ -0,0 +1,63 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "cpprest/http_msg.h" +#include "odata/edm/edm_model.h" +#include "odata/core/odata_uri_parser.h" +#include "query_target.h" + +namespace odata { namespace service +{ + +class odata_request_context +{ +public: + odata_request_context(::std::shared_ptr<::web::http::http_request> request, ::std::shared_ptr<::odata::edm::edm_model> model); + ~odata_request_context() {}; + + ::std::shared_ptr<::odata::core::odata_path> get_odata_path() + { + return m_odata_uri->path(); + } + + ::std::shared_ptr<::odata::core::odata_filter_clause> get_filter_clause() + { + return m_odata_uri->filter_clause(); + } + + ::std::shared_ptr<::odata::core::odata_orderby_clause> get_orderby_clause() + { + return m_odata_uri->orderby_clause(); + } + + ::std::shared_ptr get_query_target() + { + return query_target::resolve_path(get_odata_path()); + } + + ::odata::utility::string_t &get_request_body() + { + return m_request_body; + } + + ::web::http::http_headers& request_headers() + { + return m_request->headers(); + } + +private: + ::std::shared_ptr<::web::http::http_request> m_request; + ::std::shared_ptr<::odata::edm::edm_model> m_model; + ::std::shared_ptr<::odata::core::odata_uri_parser> m_uri_parser; + ::std::shared_ptr<::odata::core::odata_uri> m_odata_uri; + + //TODO: temporary member + ::odata::utility::string_t m_request_body; +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_select_expand_clause.cpp b/tests/e2e_service/odata_select_expand_clause.cpp new file mode 100644 index 0000000..08cb5b7 --- /dev/null +++ b/tests/e2e_service/odata_select_expand_clause.cpp @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata_select_expand_clause.h" + +namespace odata { namespace service +{ + +odata_select_expand_clause::odata_select_expand_clause() +{ +} + + +odata_select_expand_clause::~odata_select_expand_clause() +{ +} + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_select_expand_clause.h b/tests/e2e_service/odata_select_expand_clause.h new file mode 100644 index 0000000..f9e07d1 --- /dev/null +++ b/tests/e2e_service/odata_select_expand_clause.h @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +namespace odata { namespace service +{ + +class odata_select_expand_clause +{ +public: + odata_select_expand_clause(); + ~odata_select_expand_clause(); +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_service_exception.h b/tests/e2e_service/odata_service_exception.h new file mode 100644 index 0000000..d85765f --- /dev/null +++ b/tests/e2e_service/odata_service_exception.h @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "cpprest/asyncrt_utils.h" + +namespace odata { namespace service +{ + +/// +/// Represents an OData service operation exception +/// +class odata_service_exception : public std::exception +{ +public: + + /// + /// Constructor + /// + /// A string value containing the service error. + explicit odata_service_exception(::odata::utility::string_t error) : m_error(error) + { + } + + /// + /// Destructor + /// + ~odata_service_exception() _noexcept {} + + const ::odata::utility::string_t& what() + { + return m_error; + } + +private: + ::odata::utility::string_t m_error; +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_test_service.cpp b/tests/e2e_service/odata_test_service.cpp new file mode 100644 index 0000000..8ffe906 --- /dev/null +++ b/tests/e2e_service/odata_test_service.cpp @@ -0,0 +1,198 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include +#include + +#include +#include + +#include "data_source_task.h" +#include "query_handler.h" +#include "create_handler.h" +#include "odata_test_service.h" + +using namespace std; +using namespace web; +using namespace http; +using namespace utility; +using namespace http::experimental::listener; + +using namespace ::odata::core; +using namespace ::odata::edm; +using namespace concurrency::streams; + + +namespace odata { namespace service +{ + +// TODO replace ::odata::utility::string_t with web::url +odata_test_service::odata_test_service(::odata::utility::string_t url, std::shared_ptr<::odata::edm::edm_model> model, std::shared_ptr service_document) + : m_listener(url), m_model(model), m_service_document(service_document) +{ + m_uri_parser = std::make_shared<::odata::core::odata_uri_parser>(model); + m_service_root = web::uri(url); + m_listener.support(methods::GET, std::bind(&odata_test_service::handle_get, this, std::placeholders::_1)); + m_listener.support(methods::POST, std::bind(&odata_test_service::handle_create, this, std::placeholders::_1)); +} + +void odata_test_service::initialize_datasource() +{ + shared_ptr ds_task = make_shared(); + ds_task->run(); +} + +void odata_test_service::handle_error(pplx::task& t) +{ + try + { + t.get(); + } + catch(...) + { + // Ignore the error, Log it if a logger is available + } +} + +pplx::task odata_test_service::open() +{ + return m_listener.open().then(std::bind(&handle_error, std::placeholders::_1)); +} + +pplx::task odata_test_service::close() +{ + return m_listener.close().then(std::bind(&handle_error, std::placeholders::_1)); +} + +// Handler to process HTTP::GET requests. +// Replies to the request with data. +void odata_test_service::handle_get(http_request message) +{ + try + { + auto parsed_uri = m_uri_parser->parse_uri(message.relative_uri()); + + odata_message_writer writer(m_model, m_service_root); + ::odata::utility::string_t content; + if (parsed_uri->is_service_document()) + { + // Write service document + content = writer.write_service_document(m_service_document); + } + else if (parsed_uri->is_metadata_document()) + { + // Write metadata document + content = writer.write_metadata_document(); + } + else + { + shared_ptr handler = make_shared(make_shared(message), m_model); + return handler->handle(); + } + + message.reply(status_codes::OK, content).then(std::bind(&handle_error, std::placeholders::_1)); + } + catch (::odata::core::odata_exception &e) + { + message.reply(status_codes::OK, U("Exception: ") + e.what()).then(std::bind(&handle_error, std::placeholders::_1)); + } +} + +// Handler to process HTTP::POST requests. +void odata_test_service::handle_create(http_request message) +{ + try + { + shared_ptr handler = make_shared(make_shared(message), m_model); + return handler->handle(); + } + catch (::odata::core::odata_exception &e) + { + message.reply(status_codes::OK, U("Exception: ") + e.what()).then(std::bind(&handle_error, std::placeholders::_1)); + } +} + +}} + +using namespace ::odata::service; + +int _tmain(int argc, _TCHAR* argv[]) +{ + const char* test_edm_model = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + std::istringstream iss(std::move(std::string(test_edm_model))); + auto model_reader = std::make_shared<::odata::edm::edm_model_reader>(iss); + model_reader->parse(); + auto model = model_reader->get_model(); + + std::shared_ptr service_document = std::make_shared(); + service_document->add_service_document_element(std::make_shared(U("People"), U("People"), ENTITY_SET)); + service_document->add_service_document_element(std::make_shared(U("Airports"), U("Airports"), ENTITY_SET)); + + std::wstring address = U("http://localhost:4789"); + + odata_test_service listener(address, model, service_document); + listener.initialize_datasource(); + listener.open().wait(); + + std::wcout << ::odata::utility::string_t(U("Listening for requests at: ")) << address << std::endl; + + std::string line; + std::wcout << U("Hit Enter to close the listener."); + std::getline(std::cin, line); + + listener.close().wait(); + + return 0; +} \ No newline at end of file diff --git a/tests/e2e_service/odata_test_service.h b/tests/e2e_service/odata_test_service.h new file mode 100644 index 0000000..1378af6 --- /dev/null +++ b/tests/e2e_service/odata_test_service.h @@ -0,0 +1,53 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "cpprest/http_listener.h" +#include "cpprest/json.h" +#include +#include +#include +#include "odata/edm/edm_model_reader.h" +#include "odata/core/odata_uri_parser.h" +#include "odata/core/odata_message_writer.h" +#include "odata/core/odata_service_document.h" +#include "odata/core/odata_select_expand_clause.h" +#include "odata/core/odata_uri.h" +#include "odata/edm/edm_type.h" +#include "odata/common/utility.h" + +namespace odata { namespace service +{ + +class odata_test_service +{ +public: + odata_test_service(::odata::utility::string_t url, std::shared_ptr<::odata::edm::edm_model> model,std::shared_ptr<::odata::core::odata_service_document> service_document); + + pplx::task open(); + pplx::task close(); + + void initialize_datasource(); + + void handle_get(web::http::http_request message); + + void handle_create(web::http::http_request message); + +private: + std::shared_ptr<::odata::core::odata_path> process_request_uri(const ::web::uri &relative_uri); + + static void handle_error(pplx::task& t); + + // HTTP listener + web::http::experimental::listener::http_listener m_listener; + std::shared_ptr<::odata::core::odata_uri_parser> m_uri_parser; + + std::shared_ptr<::odata::edm::edm_model> m_model; + std::shared_ptr<::odata::core::odata_service_document> m_service_document; + + web::uri m_service_root; +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_test_service_metadata.xml b/tests/e2e_service/odata_test_service_metadata.xml new file mode 100644 index 0000000..a00d3a0 --- /dev/null +++ b/tests/e2e_service/odata_test_service_metadata.xml @@ -0,0 +1 @@ +image/jpegConcurrencyTripsFriendsapplication/json;odata.metadata=full;IEEE754Compatible=false;odata.streaming=trueapplication/json;odata.metadata=minimal;IEEE754Compatible=false;odata.streaming=trueapplication/json;odata.metadata=none;IEEE754Compatible=false;odata.streaming=truecontainsendswithstartswithlengthindexofsubstringtolowertouppertrimconcatyearmonthdayhourminutesecondroundfloorceilingcastisof \ No newline at end of file diff --git a/tests/e2e_service/odata_value_builder.cpp b/tests/e2e_service/odata_value_builder.cpp new file mode 100644 index 0000000..70aa8ab --- /dev/null +++ b/tests/e2e_service/odata_value_builder.cpp @@ -0,0 +1,385 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata_value_builder.h" +#include "odata_service_exception.h" + +using namespace ::odata::edm; +using namespace ::odata::core; + +namespace odata { namespace service +{ + std::shared_ptr odata_value_builder::build_odata_value(std::shared_ptr edm_type, const std::vector<::mongo::BSONObj> &data) + { + if (edm_type->get_type_kind() == edm_type_kind_t::Collection) + { + auto collection_type = std::dynamic_pointer_cast(edm_type); + auto element_type = collection_type->get_element_type(); + + if (element_type->get_type_kind() == edm_type_kind_t::Entity) + { + auto collection_value = std::make_shared(collection_type); + collection_value->set_is_top_level(true); + + for (auto iter = data.cbegin(); iter != data.cend(); iter++) + { + collection_value->add_collection_value(build_odata_value(element_type, *iter)); + } + + return collection_value; + } + + } + + if (data.size() > 1) + { + //throw exception: internal server error + } + + std::shared_ptr ret_value; + if (data.size() == 1) + { + ret_value = build_odata_value(edm_type, data[0]); + } + else + { + //nothing found. should return 204, or possibly 404. + ret_value = std::make_shared(edm_type); + } + ret_value->set_is_top_level(true); + + return ret_value; + } + + + std::shared_ptr odata_value_builder::build_odata_value(std::shared_ptr edm_type, ::mongo::BSONObj data) + { + std::shared_ptr ret_value; + + switch (edm_type->get_type_kind()) + { + case edm_type_kind_t::Primitive: + { + auto primitive_type = std::dynamic_pointer_cast(edm_type); + return build_odata_primitive_value(primitive_type, data.firstElement()); + } + case edm_type_kind_t::Enum: + { + auto enum_type = std::dynamic_pointer_cast(edm_type); + return build_odata_enum_value(enum_type, data.firstElement()); + } + case edm_type_kind_t::Collection: + { + auto collection_type = std::dynamic_pointer_cast(edm_type); + return build_odata_collection_value(collection_type, data.firstElement()); + } + case edm_type_kind_t::Entity: + { + auto entity_type = std::dynamic_pointer_cast(edm_type); + return build_odata_structured_value(entity_type, data); + } + + case edm_type_kind_t::Complex: + { + auto complex_type = std::dynamic_pointer_cast(edm_type); + return build_odata_structured_value(complex_type, data.firstElement().Obj()); + } + default: + { + throw new odata_service_exception(U("Service does not support yet.")); + } + break; + } + + } + + std::shared_ptr odata_value_builder::build_odata_structured_value(std::shared_ptr edm_type, ::mongo::BSONObj data) + { + std::shared_ptr ret_value; + + if (edm_type->get_type_kind() == edm_type_kind_t::Entity) + { + auto entity_type = std::dynamic_pointer_cast(edm_type); + ret_value = std::make_shared(entity_type); + } + else + { + auto complex_type = std::dynamic_pointer_cast(edm_type); + ret_value = std::make_shared(complex_type); + } + + for (auto iter = edm_type->begin(); iter != edm_type->end(); iter++) + { + ::odata::utility::string_t property_name = (*iter).first; + auto property_type = (*iter).second; + + auto property_value_type = property_type->get_property_type(); + std::string property_name_s(property_name.cbegin(), property_name.cend()); + switch (property_value_type->get_type_kind()) + { + case edm_type_kind_t::Collection: + { + auto collection_type = std::dynamic_pointer_cast(property_value_type); + auto collection_value = build_odata_collection_value(collection_type, data.getField(property_name_s)); + + ret_value->set_value(property_name, collection_value); + } + break; + case edm_type_kind_t::Primitive: + { + auto primitive_type = std::dynamic_pointer_cast(property_value_type); + auto primitive_value = build_odata_primitive_value(primitive_type, data.getField(property_name_s)); + + ret_value->set_value(property_name, primitive_value); + } + break; + + case edm_type_kind_t::Enum: + { + auto enum_type = std::dynamic_pointer_cast(property_value_type); + auto enum_value = build_odata_enum_value(enum_type, data.getField(property_name_s)); + + ret_value->set_value(property_name, enum_value); + } + break; + case edm_type_kind_t::Complex: + { + auto complex_type = std::dynamic_pointer_cast(property_value_type); + + mongo::BSONElement field = data.getField(property_name_s); + if (field.eoo()) + { + //The field is not found. Either the data is broken or the field is null + } + else + { + auto structured_value = build_odata_structured_value(complex_type, field.Obj()); //catch exceptions + ret_value->set_value(property_name, std::dynamic_pointer_cast(structured_value)); + } + } + break; + default: + { + + } + break; + } + + } + + return ret_value; + } + + std::shared_ptr odata_value_builder::build_odata_collection_value(std::shared_ptr edm_type, ::mongo::BSONElement data) + { + auto collection_type = std::dynamic_pointer_cast(edm_type); + auto collection_value = std::make_shared(collection_type); + + if (data.eoo()) + { + //The field is not found. Either the data is broken or the field is null + return nullptr; + } + else + { + auto element_type = collection_type->get_element_type(); + auto elements = data.Array(); //catch exceptions + for (auto iter = elements.cbegin(); iter != elements.cend(); iter++) + { + switch (element_type->get_type_kind()) + { + case edm_type_kind_t::Primitive: + { + auto primitive_type = std::dynamic_pointer_cast(element_type); + collection_value->add_collection_value(build_odata_primitive_value(primitive_type, *iter)); + } + break; + case edm_type_kind_t::Enum: + { + auto enum_type = std::dynamic_pointer_cast(element_type); + collection_value->add_collection_value(build_odata_enum_value(enum_type, *iter)); + } + break; + case edm_type_kind_t::Collection: + { + auto collection_type = std::dynamic_pointer_cast(element_type); + collection_value->add_collection_value(build_odata_collection_value(collection_type, *iter)); + } + break; + case edm_type_kind_t::Complex: + case edm_type_kind_t::Entity: + { + auto structured_type = std::dynamic_pointer_cast(element_type); + collection_value->add_collection_value(build_odata_structured_value(structured_type, iter->Obj())); + } + break; + default: + { + throw new odata_service_exception(U("Invalid edm type.")); + } + break; + } + + } + + return collection_value; + } + + } + + + std::shared_ptr odata_value_builder::build_odata_primitive_value(std::shared_ptr edm_type, ::mongo::BSONElement data) + { + + if (data.eoo()) + { + //The field is not found. Either the data is broken or the field is null + return nullptr; + } + else + { + switch (edm_type->get_primitive_kind()) + { + case edm_primitive_type_kind_t::Guid: + case edm_primitive_type_kind_t::String: + { + std::string string_value = data.String(); //catch exceptions + return std::make_shared(edm_primitive_type::STRING(), ::utility::conversions::to_string_t(string_value)); + } + break; + case edm_primitive_type_kind_t::Boolean: + { + bool bool_value = data.Bool(); //catch exceptions + return std::make_shared(edm_primitive_type::BOOLEAN(), ::utility::conversions::print_string(bool_value)); + } + break; + case edm_primitive_type_kind_t::Int16: + { + int int16_value = data.Int(); //stored as int32; catch exceptions + return std::make_shared(edm_primitive_type::INT16(), ::utility::conversions::print_string(int16_value)); + } + break; + case edm_primitive_type_kind_t::Int32: + { + int int32_value = data.Int(); //catch exceptions + return std::make_shared(edm_primitive_type::INT32(), ::utility::conversions::print_string(int32_value)); + } + break; + case edm_primitive_type_kind_t::Int64: + { + long long int64_value = data.Long(); //catch exceptions + return std::make_shared(edm_primitive_type::INT64(), ::utility::conversions::print_string(int64_value)); + } + break; + default: + { + throw new odata_service_exception(U("service does not support the primitive type.")); + } + break; + } + } + } + + std::shared_ptr odata_value_builder::build_odata_enum_value(std::shared_ptr edm_type, ::mongo::BSONElement data) + { + if (data.eoo()) + { + //The field is not found. Either the data is broken or the field is null + return nullptr; + } + else + { + std::string enum_value = data.String(); //catch exceptions + return std::make_shared(edm_type, ::utility::conversions::to_string_t(enum_value)); + } + } + + ::mongo::BSONObj odata_value_builder::build_BSONObj(std::shared_ptr odata_value) + { + if (odata_value) + { + return build_BSONObj(odata_value->get_value_type(), odata_value); + } + + return ::mongo::BSONObj(); + } + + ::mongo::BSONObj odata_value_builder::build_BSONObj(std::shared_ptr edm_type, std::shared_ptr odata_value) + { + if (edm_type && odata_value) + { + switch (edm_type->get_type_kind()) + { + case edm_type_kind_t::Entity: + { + auto structured_value = std::dynamic_pointer_cast(odata_value); + return build_BSONObj_from_structured_value(structured_value); + } + break; + default: + break; + } + } + + return ::mongo::BSONObj(); + } + + ::mongo::BSONObj odata_value_builder::build_BSONObj_from_structured_value(std::shared_ptr odata_value) + { + ::mongo::BSONObjBuilder builder; + + for (auto iter = odata_value->properties().cbegin(); iter != odata_value->properties().cend(); iter++) + { + std::string property_name(iter->first.cbegin(), iter->first.cend()); + + if (!iter->second) + { + builder << property_name << ::mongo::BSONObj(); + } + else + { + auto property_edm_type = iter->second->get_value_type(); + switch (property_edm_type->get_type_kind()) + { + case edm_type_kind_t::Primitive: + { + auto primitive_type = std::dynamic_pointer_cast(property_edm_type); + auto primitive_value = std::dynamic_pointer_cast(iter->second); + switch (primitive_type->get_primitive_kind()) + { + case edm_primitive_type_kind_t::String: + { + auto str_value = primitive_value->as<::odata::utility::string_t>(); + std::string std_str(str_value.cbegin(), str_value.cend()); + builder << property_name << std_str; + } + break; + case edm_primitive_type_kind_t::Int32: + { + builder << property_name << primitive_value->as(); + } + break; + default: + break; + } + } + break; + case edm_type_kind_t::Complex: + { + auto structured_value = std::dynamic_pointer_cast(iter->second); + builder << property_name << build_BSONObj_from_structured_value(structured_value); + } + break; + default: + break; + } + } + } + + return builder.obj(); + } + +}} \ No newline at end of file diff --git a/tests/e2e_service/odata_value_builder.h b/tests/e2e_service/odata_value_builder.h new file mode 100644 index 0000000..0c6f29d --- /dev/null +++ b/tests/e2e_service/odata_value_builder.h @@ -0,0 +1,51 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +#include +#include + +#include "odata/core/odata_value.h" +#include "odata/core/odata_enum_value.h" +#include "odata/core/odata_entity_value.h" +#include "odata/core/odata_complex_value.h" +#include "odata/core/odata_collection_value.h" + +namespace odata { namespace service +{ + +class odata_value_builder +{ +public: + odata_value_builder() {}; + + std::shared_ptr<::odata::core::odata_value> build_odata_value(std::shared_ptr<::odata::edm::edm_named_type> edm_type, const std::vector<::mongo::BSONObj> &data); + + std::shared_ptr<::odata::core::odata_value> build_odata_value(std::shared_ptr<::odata::edm::edm_named_type> edm_type, ::mongo::BSONObj data); + + std::shared_ptr<::odata::core::odata_value> build_odata_value(std::shared_ptr<::odata::edm::edm_named_type> edm_type, ::mongo::BSONElement data); + + std::shared_ptr<::odata::core::odata_structured_value> build_odata_structured_value(std::shared_ptr<::odata::edm::edm_structured_type> edm_type, ::mongo::BSONObj data); + + std::shared_ptr<::odata::core::odata_collection_value> build_odata_collection_value(std::shared_ptr<::odata::edm::edm_collection_type> edm_type, ::mongo::BSONElement data); + + std::shared_ptr<::odata::core::odata_primitive_value> build_odata_primitive_value(std::shared_ptr<::odata::edm::edm_primitive_type> edm_type, ::mongo::BSONElement data); + + std::shared_ptr<::odata::core::odata_enum_value> build_odata_enum_value(std::shared_ptr<::odata::edm::edm_enum_type> edm_type, ::mongo::BSONElement data); + + ::mongo::BSONObj build_BSONObj(std::shared_ptr<::odata::core::odata_value> odata_value); + + ::mongo::BSONObj build_BSONObj(std::shared_ptr<::odata::edm::edm_named_type> edm_type, std::shared_ptr<::odata::core::odata_value> odata_value); + + ::mongo::BSONObj build_BSONObj_from_structured_value(std::shared_ptr<::odata::core::odata_structured_value> odata_value); +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/query_handler.cpp b/tests/e2e_service/query_handler.cpp new file mode 100644 index 0000000..3855ef5 --- /dev/null +++ b/tests/e2e_service/query_handler.cpp @@ -0,0 +1,71 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "query_handler.h" +#include "data_source_task.h" +#include "odata_value_builder.h" +#include "odata_service_exception.h" +#include "odata/core/odata_message_writer.h" +#include "odata/core/odata_context_url_builder.h" + +using namespace ::std; +using namespace ::web::http; +using namespace ::utility; +using namespace ::odata::edm; +using namespace ::odata::core; + +namespace odata { namespace service +{ + query_handler::query_handler(shared_ptr request, shared_ptr model): m_request(request), m_model(model) + { + m_request_context = make_shared(m_request, m_model); + } + + void query_handler::handle() + { + ::odata::utility::string_t media_type = U("application/json"); + + auto target = m_request_context->get_query_target(); + + shared_ptr ds_task = make_shared(m_request_context); + const std::vector<::mongo::BSONObj> &result = ds_task->run(); + + auto value_builder = make_shared(); + auto target_value = value_builder->build_odata_value(target->target_edm_type(), result); + + uri context_url; + odata_context_url_builder context_url_builder(m_model, U("http://localhost:4789")); + if (target->target_edm_type()->get_type_kind() == edm_type_kind_t::Collection) + { + auto collection_type = std::dynamic_pointer_cast(target->target_edm_type()); + auto element_type = collection_type->get_element_type(); + if (element_type->get_type_kind() == edm_type_kind_t::Entity) + { + auto target_entity_set = std::dynamic_pointer_cast(target->target_navigation_source()); + context_url = context_url_builder.get_context_uri_for_collection_of_entities(target_entity_set); + } + } + else if (target->target_edm_type()->get_type_kind() == edm_type_kind_t::Entity) + { + auto entity_type = std::dynamic_pointer_cast(target->target_edm_type()); + auto target_entity_set = std::dynamic_pointer_cast(target->target_navigation_source()); + context_url = context_url_builder.get_context_uri_for_entity(target_entity_set); + } + + if (!context_url.is_empty()) + { + target_value->set_context_url(context_url); + } + + odata_message_writer writer(m_model, U("http://localhost:4789")); + auto content = writer.write_odata_value(target_value); + + http_response response(status_codes::OK); + response.set_body(content, media_type); + + m_request->reply(response).get(); + } +}} \ No newline at end of file diff --git a/tests/e2e_service/query_handler.h b/tests/e2e_service/query_handler.h new file mode 100644 index 0000000..b7e6fcf --- /dev/null +++ b/tests/e2e_service/query_handler.h @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "cpprest/http_msg.h" +#include "odata/edm/edm_model.h" +#include "odata_request_context.h" + +namespace odata { namespace service +{ + +class query_handler +{ +public: + query_handler(::std::shared_ptr<::web::http::http_request> request, ::std::shared_ptr<::odata::edm::edm_model> model); + ~query_handler() {}; + + void handle(); + +private: + ::std::shared_ptr<::web::http::http_request> m_request; + ::std::shared_ptr<::odata::edm::edm_model> m_model; + ::std::shared_ptr m_request_context; +}; + +}} \ No newline at end of file diff --git a/tests/e2e_service/query_node_visitor.cpp b/tests/e2e_service/query_node_visitor.cpp new file mode 100644 index 0000000..d8d1ba9 --- /dev/null +++ b/tests/e2e_service/query_node_visitor.cpp @@ -0,0 +1,198 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "query_node_visitor.h" +#include "odata_service_exception.h" + +using namespace ::odata::core; +using namespace ::odata::edm; + +namespace odata { namespace service +{ + +::mongo::BSONObj node_to_bson_visitor::translate_node(std::shared_ptr node) +{ + if (!node) + { + throw new odata_service_exception(U("unexpected null node")); + } + + return node->accept(shared_from_this()); +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + if (!node) + { + throw new odata_service_exception(U("unexpected null node")); + } + + auto constant_value = node->value(); + auto primitive_type = std::dynamic_pointer_cast(constant_value->get_value_type()); + switch (primitive_type->get_primitive_kind()) + { + case edm_primitive_type_kind_t::String: + { + auto string_value = constant_value->as<::odata::utility::string_t>(); + ::std::string string_value_s(string_value.cbegin(), string_value.cend()); + return BSON("query" << string_value_s); + } + break; + case edm_primitive_type_kind_t::Int32: + { + auto int_value = constant_value->as(); + return BSON("query" << int_value); + } + break; + default: + { + throw new odata_service_exception(U("service does not implement the primitive type in $query option")); + } + break; + } +} + + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + if (!node) + { + throw new odata_service_exception(U("unexpected null node")); + } + + auto left = node->left(); + auto right = node->right(); + + auto left_exp = translate_node(left); + auto right_exp = translate_node(right); + + switch (node->operator_kind()) + { + case binary_operator_kind::Equal: + { + auto field_name = left_exp["query"].String(); //catch exceptions + auto right_element = right_exp["query"]; + switch (right_element.type()) + { + case ::mongo::BSONType::String: + { + return BSON("query" << BSON(field_name << right_element.String())); + } + break; + case ::mongo::BSONType::NumberInt: + { + return BSON("query" << BSON(field_name << right_element.Int())); + } + break; + default: + { + throw new odata_service_exception(U("service does not support the right node")); + } + } + + } + break; + case binary_operator_kind::GreaterThan: + case binary_operator_kind::GreaterThanOrEqual: + case binary_operator_kind::LessThan: + case binary_operator_kind::LessThanOrEqual: + { + std::string binary_operator; + if (node->operator_kind() == binary_operator_kind::GreaterThan) + { + binary_operator = "$gt"; + } + else if (node->operator_kind() == binary_operator_kind::GreaterThanOrEqual) + { + binary_operator = "$gte"; + } + else if (node->operator_kind() == binary_operator_kind::LessThan) + { + binary_operator = "$lt"; + } + else if (node->operator_kind() == binary_operator_kind::LessThanOrEqual) + { + binary_operator = "$lte"; + } + auto field_name = left_exp["query"].String(); //catch exceptions + auto right_element = right_exp["query"]; + switch (right_element.type()) + { + case ::mongo::BSONType::String: + { + return BSON("query" << BSON(field_name << BSON(binary_operator << right_element.String()))); + } + break; + case ::mongo::BSONType::NumberInt: + { + return BSON("query" << BSON(field_name << BSON(binary_operator << right_element.Int()))); + } + break; + default: + { + throw new odata_service_exception(U("service does not support the right node")); + } + } + } + break; + default: + { + throw new odata_service_exception(U("service does not support the binary operator")); + } + break; + } +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + throw new odata_service_exception(U("service does not implement the node type")); +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + throw new odata_service_exception(U("service does not implement the node type")); +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + if (!node) + { + throw new odata_service_exception(U("unexpected null node")); + } + + auto property_name = node->property_name(); + std::string property_name_s(property_name.cbegin(), property_name.cend()); + return BSON("query" << property_name_s); +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + throw new odata_service_exception(U("service does not implement the node type")); +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + throw new odata_service_exception(U("service does not implement the node type")); +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + throw new odata_service_exception(U("service does not implement the node type")); +} + +::mongo::BSONObj node_to_bson_visitor::visit(std::shared_ptr node) +{ + throw new odata_service_exception(U("service does not implement the node type")); +} + +::mongo::BSONObj node_to_bson_visitor::visit_any(std::shared_ptr node) +{ + throw new odata_service_exception(U("service does not implement the node type")); +} + +}} diff --git a/tests/e2e_service/query_node_visitor.h b/tests/e2e_service/query_node_visitor.h new file mode 100644 index 0000000..0680f4a --- /dev/null +++ b/tests/e2e_service/query_node_visitor.h @@ -0,0 +1,48 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include +#include +#include + +#include + +#include "cpprest/basic_types.h" +#include "odata/core/odata_query_node_visitor.h" +#include "odata_service_exception.h" + +namespace odata { namespace service +{ + +class node_to_bson_visitor : public ::odata::core::odata_query_node_visitor<::mongo::BSONObj> +{ +public: + ::mongo::BSONObj translate_node(std::shared_ptr<::odata::core::odata_query_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_constant_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_binary_operator_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_unary_operator_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_parameter_alias_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_property_access_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_type_cast_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_lambda_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_range_variable_node> node); + + ::mongo::BSONObj visit(std::shared_ptr<::odata::core::odata_function_call_node> node); + + ::mongo::BSONObj visit_any(std::shared_ptr<::odata::core::odata_query_node> node); +}; + +}} diff --git a/tests/e2e_service/query_target.cpp b/tests/e2e_service/query_target.cpp new file mode 100644 index 0000000..e1eb05b --- /dev/null +++ b/tests/e2e_service/query_target.cpp @@ -0,0 +1,86 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "query_target.h" +#include "odata_service_exception.h" + +using namespace ::odata::core; +using namespace ::odata::edm; + +namespace odata { namespace service +{ + std::shared_ptr query_target::target_edm_type() const + { + return m_edm_type; + } + + std::shared_ptr query_target::target_navigation_source() const + { + return m_navigation_source; + } + + std::shared_ptr query_target::resolve_path(std::shared_ptr path) + { + auto target = std::make_shared(); + if (path) + { + for (auto iter = path->segments().cbegin(); iter != path->segments().cend(); iter++) + { + target->handle(*iter); + } + } + + return target; + } + + void query_target::handle(std::shared_ptr segment) + { + switch (segment->segment_type()) + { + case odata_path_segment_type::EntitySet: + handle(std::dynamic_pointer_cast(segment)); + break; + case odata_path_segment_type::Key: + handle(std::dynamic_pointer_cast(segment)); + break; + case odata_path_segment_type::StructuralProperty: + handle(std::dynamic_pointer_cast(segment)); + break; + case odata_path_segment_type::NavigationProperty: + handle(std::dynamic_pointer_cast(segment)); + break; + default: + throw new odata_service_exception(U("segment not supported.")); + } + } + + void query_target::handle(std::shared_ptr segment) + { + m_navigation_source = segment->entity_set(); + m_edm_type = std::make_shared(segment->entity_type()); + } + + void query_target::handle(std::shared_ptr segment) + { + auto collection_type = std::dynamic_pointer_cast(m_edm_type); + if (collection_type) + { + m_edm_type = collection_type->get_element_type(); + } + } + + void query_target::handle(std::shared_ptr segment) + { + m_edm_type = segment->property()->get_property_type(); + } + + void query_target::handle(std::shared_ptr segment) + { + m_navigation_source = segment->navigation_type()->get_binded_navigation_source(); + m_edm_type = segment->navigation_type()->get_navigation_type(); + } + +}} \ No newline at end of file diff --git a/tests/e2e_service/query_target.h b/tests/e2e_service/query_target.h new file mode 100644 index 0000000..1c57c5c --- /dev/null +++ b/tests/e2e_service/query_target.h @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/edm/edm_model.h" +#include "odata/core/odata_path.h" +#include "odata/core/odata_path_segment_visitor.h" + +namespace odata { namespace service +{ + +class query_target +{ +private: + std::shared_ptr<::odata::edm::edm_navigation_source> m_navigation_source; + std::shared_ptr<::odata::edm::edm_named_type> m_edm_type; + +public: + query_target() {}; + ~query_target() {}; + + std::shared_ptr<::odata::edm::edm_named_type> target_edm_type() const; + + std::shared_ptr<::odata::edm::edm_navigation_source> target_navigation_source() const; + + static std::shared_ptr resolve_path(std::shared_ptr<::odata::core::odata_path> path); + + void handle(std::shared_ptr<::odata::core::odata_path_segment> segment); + + void handle(std::shared_ptr<::odata::core::odata_entity_set_segment> segment); + + void handle(std::shared_ptr<::odata::core::odata_key_segment> segment); + + void handle(std::shared_ptr<::odata::core::odata_structural_property_segment> segment); + + void handle(std::shared_ptr<::odata::core::odata_navigation_property_segment> segment); + +}; + +}} \ No newline at end of file diff --git a/tests/framework/CMakeLists.txt b/tests/framework/CMakeLists.txt new file mode 100644 index 0000000..8be5176 --- /dev/null +++ b/tests/framework/CMakeLists.txt @@ -0,0 +1,3 @@ +add_subdirectory(utilities) +add_subdirectory(UnitTestpp) +add_subdirectory(TestRunner) diff --git a/tests/framework/TestRunner/CMakeLists.txt b/tests/framework/TestRunner/CMakeLists.txt new file mode 100644 index 0000000..fc3ae8a --- /dev/null +++ b/tests/framework/TestRunner/CMakeLists.txt @@ -0,0 +1,73 @@ +include_directories( + ${UnitTestpp_INCLUDE_DIR} + ${Boost_INCLUDE_DIR} + ) + +set(TR_SOURCES + test_runner.cpp + test_module_loader.cpp + ) + +add_definitions(-DDESKTOP_TEST_RUNNER) + +if(BUILD_SHARED_LIBS) + add_executable(test_runner + test_runner.cpp + test_module_loader.cpp + ) + + target_link_libraries(test_runner + ${Boost_FRAMEWORK} + ${Boost_SYSTEM_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${LIB}unittestpp + ${CMAKE_DL_LIBS} + ) +else() + if (APPLE) + add_executable(test_runner + test_runner.cpp + test_module_loader.cpp + ) + + target_link_libraries(test_runner + ${Boost_FRAMEWORK} + ${Boost_SYSTEM_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${LIB}unittestpp + ${CMAKE_DL_LIBS} + ) + elseif(UNIX) + add_executable(test_runner + test_runner.cpp + test_module_loader.cpp + ) + + target_link_libraries(test_runner + ${Boost_FRAMEWORK} + ${Boost_SYSTEM_LIBRARY} + ${Boost_FILESYSTEM_LIBRARY} + ${LIB}unittestpp + ${CMAKE_DL_LIBS} + -Wl,--no-whole-archive + ) + else() + # In order to achieve --whole-archive on windows, we link all the test files into the test_runner directly + # This means that the tests themselves must be created as "OBJECT" libraries + add_executable(test_runner + test_runner.cpp + test_module_loader.cpp + ) + target_link_libraries(test_runner + ${LIB}unittestpp + ${CMAKE_DL_LIBS} + ) + endif() +endif() + +# add_custom_target(run_tests ALL DEPENDS run_tests.sh) +# add_custom_command( +# OUTPUT run_tests.sh +# COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_SOURCE_DIR}/run_tests.sh $ +# DEPENDS run_tests.sh +# ) diff --git a/tests/framework/TestRunner/run_tests.sh b/tests/framework/TestRunner/run_tests.sh new file mode 100755 index 0000000..6e49100 --- /dev/null +++ b/tests/framework/TestRunner/run_tests.sh @@ -0,0 +1,30 @@ +#!/bin/bash + +DEBUGGER="" +ENABLE_DEBUG_MODE="" +if [ "$1" = "--debug" ]; then + DEBUGGER="gdb -args" # -q -x automate_gdb -args" + ENABLE_DEBUG_MODE="/breakonerror" +fi + +export LD_LIBRARY_PATH=".:$LD_LIBRARY_PATH" + +exit_code=0 + +for test_lib in *_test.so; do + $DEBUGGER ./test_runner $ENABLE_DEBUG_MODE $test_lib + if [ $? -ne 0 ] + then + echo "Test suite failed" + exit_code=1 + fi +done +if [ $exit_code -eq 0 ]; +then + echo "All tests finished successfully" +else + echo "Some tests failed" +fi +date +exit $exit_code + diff --git a/tests/framework/TestRunner/test_module_loader.cpp b/tests/framework/TestRunner/test_module_loader.cpp new file mode 100644 index 0000000..a37f1a8 --- /dev/null +++ b/tests/framework/TestRunner/test_module_loader.cpp @@ -0,0 +1,183 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#ifdef WIN32 +#include +#include "odata/common/compat/windows_compat.h" +#elif defined(__APPLE__) +#include "odata/common/compat/apple_compat.h" +#else +#include "odata/common/compat/linux_compat.h" +#endif + +#include "test_module_loader.h" +#include + +#ifdef WIN32 + +// Windows module +class windows_module : public test_module +{ +public: + windows_module(const std::string &dllName) : test_module(dllName), m_hModule(nullptr) {} + + GetTestsFunc get_test_list() + { + return (GetTestsFunc)GetProcAddress(m_hModule, "GetTestList"); + } + +protected: + + virtual unsigned long load_impl() + { + // Make sure ends in .dll + if(*(m_dllName.end() - 1) != 'l' + || *(m_dllName.end() - 2) != 'l' + || *(m_dllName.end() - 3) != 'd' + || *(m_dllName.end() - 4) != '.') + { + return (unsigned long)-1; + } + m_hModule = LoadLibraryA(m_dllName.c_str()); + if(m_hModule == nullptr) + { + return GetLastError(); + } + return 0; + } + + virtual unsigned long unload_impl() + { + if(!FreeLibrary(m_hModule)) + { + return GetLastError(); + } + return 0; + } + + HMODULE m_hModule; + +private: + windows_module(const windows_module &); + windows_module & operator=(const windows_module &); + +}; + +#else +#include "dlfcn.h" +#include + +class linux_module : public test_module +{ +public: + linux_module(const std::string &soName) : test_module(soName), m_handle(nullptr) {} + + GetTestsFunc get_test_list() + { + auto ptr = dlsym(m_handle, "GetTestList"); + if (ptr == nullptr) + { + std::cerr << "couldn't find GetTestList" << +#ifdef __APPLE__ + " " << dlerror() << +#endif + std::endl; + } + return (GetTestsFunc)ptr; + } + +protected: + + virtual unsigned long load_impl() + { +#ifdef __APPLE__ + auto exe_directory = getcwd(nullptr, 0); + auto path = std::string(exe_directory) + "/" + m_dllName; + free(exe_directory); +#else + auto path = boost::filesystem::initial_path().string() + "/" + m_dllName; +#endif + + m_handle = dlopen(path.c_str(), RTLD_LAZY|RTLD_GLOBAL); + if (m_handle == nullptr) + { + std::cerr << std::string(dlerror()) << std::endl; + return -1; + } + return 0; + } + + virtual unsigned long unload_impl() + { + if (dlclose(m_handle) != 0) + { + std::cerr << std::string(dlerror()) << std::endl; + return -1; + } + return 0; + } + + void* m_handle; +}; +#endif + +test_module_loader::test_module_loader() +{ +} + +test_module_loader::~test_module_loader() +{ + for(auto iter = m_modules.begin(); iter != m_modules.end(); ++iter) + { + iter->second->unload(); + delete iter->second; + } +} + +unsigned long test_module_loader::load(const std::string &dllName) +{ + // Check if the module is already loaded. + if(m_modules.find(dllName) != m_modules.end()) + { + return 0; + } + + test_module *pModule; +#ifdef WIN32 + pModule = new windows_module(dllName); +#else + pModule = new linux_module(dllName); +#endif + + // Load dll. + const unsigned long error_code = pModule->load(); + if(error_code != 0) + { + delete pModule; + return error_code; + } + else + { + m_modules[dllName] = pModule; + } + return 0; +} + +UnitTest::TestList g_list; + +UnitTest::TestList& test_module_loader::get_test_list(const std::string &dllName) +{ + GetTestsFunc getTestsFunc = m_modules[dllName]->get_test_list(); + + // If there is no GetTestList function then it must be a dll without any tests. + // Simply return an empty TestList. + if(getTestsFunc == nullptr) + { + return g_list; + } + + return getTestsFunc(); +} diff --git a/tests/framework/TestRunner/test_module_loader.h b/tests/framework/TestRunner/test_module_loader.h new file mode 100644 index 0000000..3ed4451 --- /dev/null +++ b/tests/framework/TestRunner/test_module_loader.h @@ -0,0 +1,80 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#ifndef INCLUDED_TEST_MODULE_LOADER +#define INCLUDED_TEST_MODULE_LOADER + +#include +#include "unittestpp.h" + +// Exported function from all test dlls. +typedef UnitTest::TestList & (__cdecl *GetTestsFunc)(); + +// Interface to implement on each platform to be be able to load/unload and call global functions. +class test_module +{ +public: + test_module(const std::string &dllName) : m_dllName(dllName), m_loaded(false) {} + virtual ~test_module() {} + + unsigned long load() + { + if(!m_loaded) + { + m_loaded = true; + unsigned long error_code = load_impl(); + if(error_code != 0) + { + m_loaded = false; + } + return error_code; + } + return 0; + } + + virtual GetTestsFunc get_test_list() = 0; + + unsigned long unload() + { + if(m_loaded) + { + m_loaded = false; + return unload_impl(); + } + return 0; + } + +protected: + + virtual unsigned long load_impl() = 0; + virtual unsigned long unload_impl() = 0; + + bool m_loaded; + const std::string m_dllName; + +private: + test_module(const test_module &); + test_module & operator=(const test_module &); +}; + +// Handles organizing all test binaries and using the correct module loader. +class test_module_loader +{ +public: + test_module_loader(); + ~test_module_loader(); + + // Does't complain if module with same name is already loaded. + unsigned long load(const std::string &dllName); + + // Module must have already been loaded. + UnitTest::TestList& get_test_list(const std::string &dllName); + +private: + std::map m_modules; +}; + +#endif diff --git a/tests/framework/TestRunner/test_runner.cpp b/tests/framework/TestRunner/test_runner.cpp new file mode 100644 index 0000000..b10e862 --- /dev/null +++ b/tests/framework/TestRunner/test_runner.cpp @@ -0,0 +1,643 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +// TestRunner.cpp : Defines the entry point for the console application. +// + +#include +#include +#include +#include +#include + +#ifdef WIN32 +#include +#include +#include "odata/common/compat/windows_compat.h" +#else +#include +#ifdef __APPLE__ +#include "odata/common/compat/apple_compat.h" +#include +#else +#include +#include "odata/common/compat/linux_compat.h" +#endif +#endif + +#include "../UnitTestpp/src/TestReporterStdout.h" +#include "../UnitTestpp/src/TimeHelpers.h" +#include "../UnitTestpp/src/GlobalSettings.h" + +#include "test_module_loader.h" + +static void print_help() +{ + std::cout << "Usage: testrunner.exe [/list] [/listproperties] [/noignore] [/breakonerror]" <] [/select:@key=value] [/loop:]" << std::endl; + std::cout << std::endl; + std::cout << " /list List all the names of the test_binaries and their" << std::endl; + std::cout << " test cases." << std::endl; + std::cout << std::endl; + std::cout << " /listproperties List all the names of the test binaries, test cases, and" << std::endl; + std::cout << " test properties." << std::endl; + std::cout << std::endl; + std::cout << " /breakonerror Break into the debugger when a failure is encountered." << std::endl; + std::cout << std::endl; + std::cout << " /name: Run only test cases with matching name. Can contain the" << std::endl; + std::cout << " wildcard '*' character." << std::endl; + std::cout << std::endl; + std::cout << " /noignore Include tests even if they have the 'Ignore' property set" << std::endl; + std::cout << std::endl; + std::cout << " /select:@key=value Filter by the value of a particular test property." << std::endl; + std::cout << std::endl; + std::cout << " /loop: Run test cases a specified number of times." << std::endl; + + std::cout << std::endl; + std::cout << "Can also specify general global settings with the following:" << std::endl; + std::cout << " /global_key:global_value OR /global_key" << std::endl << std::endl; +} + +static std::string to_lower(const std::string &str) +{ + std::string lower; + for(auto iter = str.begin(); iter != str.end(); ++iter) + { + lower.push_back((char)tolower(*iter)); + } + return lower; +} + +static std::vector get_files_in_directory() +{ + std::vector files; + +#ifdef WIN32 + + char exe_directory_buffer[MAX_PATH]; + GetModuleFileNameA(NULL, exe_directory_buffer, MAX_PATH); + std::string exe_directory = to_lower(exe_directory_buffer); + auto location = exe_directory.rfind("testrunner"); + exe_directory.erase(location); + exe_directory.append("*"); + WIN32_FIND_DATAA findFileData; + HANDLE hFind = FindFirstFileA(exe_directory.c_str(), &findFileData); + if(hFind != INVALID_HANDLE_VALUE && !(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + files.push_back(findFileData.cFileName); + } + while(FindNextFileA(hFind, &findFileData) != 0) + { + if(!(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)) + { + files.push_back(findFileData.cFileName); + } + } + FindClose(hFind); + +#elif !defined(__APPLE__) + using namespace boost::filesystem; + + auto exe_directory = initial_path().string(); + for (auto it = directory_iterator(path(exe_directory)); it != directory_iterator(); ++it) + { + if (is_regular_file(*it)) + { + files.push_back(it->path().filename().string()); + } + } +#else + auto exe_directory = getcwd(nullptr, 0); + + DIR *dir = opendir(exe_directory); + free(exe_directory); + + if (dir != nullptr) + { + struct dirent *ent = readdir(dir); + while (ent != nullptr) + { + if (ent->d_type == DT_REG) + { + files.push_back(ent->d_name); + } + ent = readdir(dir); + } + closedir(dir); + } +#endif + + return files; +} + +static std::string replace_wildcard_for_regex(const std::string &str) +{ + std::string result; + for(auto iter = str.begin(); iter != str.end(); ++iter) + { + if(*iter == '*') + { + result.push_back('.'); + } + result.push_back(*iter); + } + return result; +} + +static std::vector get_matching_binaries(const std::string &dllName) +{ + std::vector matchingFiles; + + // If starts with .\ remove it. + std::string expandedDllName(dllName); + if(expandedDllName.size() > 2 && expandedDllName[0] == '.' && expandedDllName[1] == '\\') + { + expandedDllName = expandedDllName.substr(2); + } + + // Escape any '.' + size_t oldLocation = 0; + size_t location = expandedDllName.find(".", oldLocation); + while(location != std::string::npos) + { + expandedDllName.insert(expandedDllName.find(".", oldLocation), "\\"); + oldLocation = location + 2; + location = expandedDllName.find(".", oldLocation); + } + + // Replace all '*' in dllName with '.*' + expandedDllName = replace_wildcard_for_regex(expandedDllName); + + std::vector allFiles = get_files_in_directory(); + + // Filter out any files that don't match. + std::regex dllRegex(expandedDllName, std::regex_constants::icase); + + for(auto iter = allFiles.begin(); iter != allFiles.end(); ++iter) + { + if(std::regex_match(*iter, dllRegex)) + { + matchingFiles.push_back(*iter); + } + } + + return matchingFiles; +} + +static std::multimap g_properties; +static std::vector g_test_binaries; +static int g_individual_test_timeout = 60000 * 3; + +static int parse_command_line(int argc, char **argv) +{ + if(argc < 2) + { + print_help(); + return -1; + } + + for(int i = 1; i < argc; ++i) + { + std::string arg(argv[i]); + arg = to_lower(arg); + + if(arg.compare("/?") == 0) + { + print_help(); + return -1; + } + else if(arg.find("/") == 0) + { + if(arg.find("/select:@") == 0) + { + std::string prop_asgn = std::string(argv[i]).substr(std::string("/select:@").size()); + auto eqsgn = prop_asgn.find('='); + if (eqsgn < prop_asgn.size()) + { + auto key = prop_asgn.substr(0,eqsgn); + auto value = prop_asgn.substr(eqsgn+1); + g_properties.insert(std::make_pair(key, value)); + } + else + { + g_properties.insert(std::make_pair(prop_asgn, "*")); + } + } + else if(arg.find(":") != std::string::npos) + { + const size_t index = arg.find(":"); + const std::string key = std::string(argv[i]).substr(1, index - 1); + const std::string value = std::string(argv[i]).substr(index + 1); + UnitTest::GlobalSettings::Add(key, value); + } + else + { + UnitTest::GlobalSettings::Add(arg.substr(1), ""); + } + } + else if(arg.find("/debug") == 0) + { + printf("Attach debugger now...\n"); + int temp; + std::cin >> temp; + } + else + { + g_test_binaries.push_back(arg); + } + } + + return 0; +} + +static bool matched_properties(const UnitTest::TestProperties& test_props) +{ + // TestRunner can only execute either desktop or winrt tests, but not both. + // This starts with visual studio versions after VS 2012. +#if defined (_MSC_VER) && (_MSC_VER >= 1800) +#ifdef WINRT_TEST_RUNNER + UnitTest::GlobalSettings::Add("winrt", ""); +#elif defined DESKTOP_TEST_RUNNER + UnitTest::GlobalSettings::Add("desktop", ""); +#endif +#endif + + // The 'Require' property on a test case is special. + // It requires a certain global setting to be fulfilled to execute. + if(test_props.Has("Requires")) + { + const std::string requires = test_props.Get("Requires"); + std::vector requirements; + + // Can be multiple requirements, a semi colon seperated list + std::string::size_type pos = requires.find_first_of(';'); + std::string::size_type last_pos = 0; + while(pos != std::string::npos) + { + requirements.push_back(requires.substr(last_pos, pos - last_pos)); + last_pos = pos + 1; + pos = requires.find_first_of(';', last_pos); + } + requirements.push_back(requires.substr(last_pos)); + for(auto iter = requirements.begin(); iter != requirements.end(); ++iter) + { + if(!UnitTest::GlobalSettings::Has(to_lower(*iter))) + { + return false; + } + } + } + + if (g_properties.size() == 0) + return true; + + // All the properties specified at the cmd line act as a 'filter'. + for (auto iter = g_properties.begin(); iter != g_properties.end(); ++iter) + { + auto name = iter->first; + auto value = iter->second; + if (test_props.Has(name) && (value == "*" || test_props[name] == value) ) + { + return true; + } + } + return false; +} + +// Functions to list all the test cases and their properties. +static void handle_list_option(bool listProperties, const UnitTest::TestList &tests, const std::regex &nameRegex) +{ + UnitTest::Test *pTest = tests.GetFirst(); + while(pTest != nullptr) + { + std::string fullTestName = pTest->m_details.suiteName; + fullTestName.append(":"); + fullTestName.append(pTest->m_details.testName); + + if(matched_properties(pTest->m_properties) && std::regex_match(fullTestName, nameRegex)) + { + std::cout << " " << fullTestName << std::endl; + if(listProperties) + { + std::for_each(pTest->m_properties.begin(), pTest->m_properties.end(), [&](const std::pair key_value) + { + std::cout << " " << key_value.first << ": " << key_value.second << std::endl; + }); + } + } + pTest = pTest->m_nextTest; + } +} +static void handle_list_option(bool listProperties, const UnitTest::TestList &tests) +{ + handle_list_option(listProperties, tests, std::regex(".*")); +} + +static void ChangeConsoleTextColorToRed() +{ +#ifdef WIN32 + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0004 | 0x0008); +#else + std::cout << "\033[1;31m"; +#endif +} + +static void ChangeConsoleTextColorToGreen() +{ +#ifdef WIN32 + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0002 | 0x0008); +#else + std::cout << "\033[1;32m"; +#endif +} + +static void ChangeConsoleTextColorToGrey() +{ +#ifdef WIN32 + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN); +#else + std::cout << "\033[0m"; +#endif +} + +bool IsTestIgnored(UnitTest::Test *pTest) +{ + if(pTest->m_properties.Has("Ignore")) return true; +#ifdef WIN32 + if(pTest->m_properties.Has("Ignore:Windows")) return true; +#elif defined(__APPLE__) + if(pTest->m_properties.Has("Ignore:Apple")) return true; +#else +if(pTest->m_properties.Has("Ignore:Linux")) return true; +#endif + return false; +} + +// +// These are to handle cases where an exception or assert occurs on a thread +// that isn't being waited on and the process exits. These shouldn't be happening, +// but could happen if we have a bug. +// +#ifdef WIN32 + +int CrtReportHandler(int reportType, char *message, int *returnValue) +{ + std::cerr << "In CRT Report Handler. ReportType:" << reportType << ", message:" << message << std::endl; + + // Cause break into debugger. + *returnValue = 1; + return TRUE; +} + +#endif + +int main(int argc, char* argv[]) +{ +#ifdef WIN32 + // The test runner built with WinRT support might be used on a pre Win8 machine. + // Obviously in that case WinRT test cases can't run, but non WinRT ones should be + // fine. So dynamically try to call RoInitialize/RoUninitialize. + HMODULE hComBase = LoadLibrary(L"combase.dll"); + if(hComBase != nullptr) + { + typedef HRESULT (WINAPI *RoInit)(int); + RoInit roInitFunc = (RoInit)GetProcAddress(hComBase, "RoInitialize"); + if(roInitFunc != nullptr) + { + roInitFunc(1); // RO_INIT_MULTITHREADED + } + } + + _CrtSetReportHook2(_CRT_RPTHOOK_INSTALL, CrtReportHandler); + struct console_restorer { + CONSOLE_SCREEN_BUFFER_INFO m_originalConsoleInfo; + console_restorer() + { + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &m_originalConsoleInfo); + } + ~console_restorer() + { + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), m_originalConsoleInfo.wAttributes); + } + }local; +#endif + + if(parse_command_line(argc, argv) != 0) + { + return -1; + } + + if(g_test_binaries.empty()) + { + std::cout << "Error: no test binaries were specified" << std::endl; + print_help(); + return -1; + } + + test_module_loader module_loader; + int totalTestCount = 0, failedTestCount = 0; + std::vector failedTests; + UnitTest::TestReporterStdout testReporter; + + bool breakOnError = false; + if(UnitTest::GlobalSettings::Has("breakonerror")) + { + breakOnError = true; + } + + // Determine if list or listProperties. + bool listOption = false, listPropertiesOption = false; + if(UnitTest::GlobalSettings::Has("list")) + { + listOption = true; + } + if(UnitTest::GlobalSettings::Has("listproperties")) + { + listOption = true; + listPropertiesOption = true; + } + + // Start timer. + UnitTest::Timer timer; + timer.Start(); + + // Cycle through all the test binaries, load them, and perform the specified action. + + for(auto iter = g_test_binaries.begin(); iter != g_test_binaries.end(); ++iter) + { + std::vector matchingBinaries = get_matching_binaries(*iter); + if(matchingBinaries.empty()) + { + std::cout << "File '" << *iter << "' not found." << std::endl; + } + for(auto binary = matchingBinaries.begin(); binary != matchingBinaries.end(); ++binary) + { + unsigned long error_code = module_loader.load(*binary); + if(error_code != 0) + { + // Only omit an error if a wildcard wasn't used. + if(iter->find('*') == std::string::npos) + { + std::cout << "Error loading " << *binary << ": " << error_code << std::endl; + return error_code; + } + else + { + continue; + } + } + std::cout << "Loaded " << *binary << "..." << std::endl; + UnitTest::TestList& tests = module_loader.get_test_list(*binary); + + // Skip if binary contains no tests. + if(tests.IsEmpty()) + { + std::cout << "Skipping " << *binary << " because it contains no test cases" << std::endl; + continue; + } + + // Check to see if running tests multiple times + int numTimesToRun = 1; + if(UnitTest::GlobalSettings::Has("loop")) + { + std::istringstream strstream(UnitTest::GlobalSettings::Get("loop")); + strstream >> numTimesToRun; + } + + const bool include_ignored_tests = UnitTest::GlobalSettings::Has("noignore"); + + // Run test cases + for(int i = 0; i < numTimesToRun; ++i) + { + if(!listOption) + { + std::cout << "Running test cases in " << *binary << "..." << std::endl; + std::fflush(stdout); + } + + UnitTest::TestRunner testRunner(testReporter, breakOnError); + if(UnitTest::GlobalSettings::Has("name")) + { + std::regex nameRegex(replace_wildcard_for_regex(UnitTest::GlobalSettings::Get("name"))); + + if(listOption) + { + handle_list_option(listPropertiesOption, tests, nameRegex); + } + else + { + testRunner.RunTestsIf( + tests, + [&](UnitTest::Test *pTest) -> bool + { + // Combine suite and test name + std::string fullTestName = pTest->m_details.suiteName; + fullTestName.append(":"); + fullTestName.append(pTest->m_details.testName); + + if(IsTestIgnored(pTest) && !include_ignored_tests) + return false; + else + return matched_properties(pTest->m_properties) && + std::regex_match(fullTestName, nameRegex); + }, + g_individual_test_timeout); + } + } + else + { + if(listOption) + { + handle_list_option(listPropertiesOption, tests); + } + else + { + testRunner.RunTestsIf( + tests, + [&](UnitTest::Test *pTest) -> bool + { + if(IsTestIgnored(pTest) && !include_ignored_tests) + return false; + else + return matched_properties(pTest->m_properties); + }, + g_individual_test_timeout); + } + } + + if(!listOption) + { + totalTestCount += testRunner.GetTestResults()->GetTotalTestCount(); + failedTestCount += testRunner.GetTestResults()->GetFailedTestCount(); + if( totalTestCount == 0 ) + { + std::cout << "No tests were run. Check the command line syntax (must be 'TestRunner.exe SomeTestDll.dll /name:suite_name:test_name')" << std::endl; + } + else + { + if(testRunner.GetTestResults()->GetFailedTestCount() > 0) + { + ChangeConsoleTextColorToRed(); + const std::vector & failed = testRunner.GetTestResults()->GetFailedTests(); + std::for_each(failed.begin(), failed.end(), [](const std::string &failedTest) + { + std::cout << "**** " << failedTest << " FAILED ****" << std::endl << std::endl; + std::fflush(stdout); + }); + ChangeConsoleTextColorToGrey(); + } + else + { + ChangeConsoleTextColorToGreen(); + std::cout << "All test cases in " << *binary << " PASSED" << std::endl << std::endl; + ChangeConsoleTextColorToGrey(); + } + const std::vector &newFailedTests = testRunner.GetTestResults()->GetFailedTests(); + failedTests.insert(failedTests.end(), newFailedTests.begin(), newFailedTests.end()); + } + } + } + + tests.Clear(); + } + } + + if( totalTestCount > 0 ) + { + // Print out all failed test cases at the end for easy viewing. + const double elapsedTime = timer.GetTimeInMs(); + std::cout << "Finished running all tests. Took " << elapsedTime << "ms" << std::endl; + if(failedTestCount > 0) + { + ChangeConsoleTextColorToRed(); + std::for_each(failedTests.begin(), failedTests.end(), [](const std::string &failedTest) + { + std::cout << "**** " << failedTest << " FAILED ****" << std::endl; + }); + } + else + { + ChangeConsoleTextColorToGreen(); + std::cout << "****SUCCESS all " << totalTestCount << " test cases PASSED****" << std::endl; + } + ChangeConsoleTextColorToGrey(); + } + +#ifdef WIN32 + if(hComBase != nullptr) + { + typedef void (WINAPI *RoUnInit)(); + RoUnInit roUnInitFunc = (RoUnInit)GetProcAddress(hComBase, "RoUninitialize"); + if(roUnInitFunc != nullptr) + { + roUnInitFunc(); + } + FreeLibrary(hComBase); + } +#endif + + return failedTestCount; +} diff --git a/tests/framework/UnitTestpp/CMakeLists.txt b/tests/framework/UnitTestpp/CMakeLists.txt new file mode 100644 index 0000000..be75f1d --- /dev/null +++ b/tests/framework/UnitTestpp/CMakeLists.txt @@ -0,0 +1,47 @@ +include_directories(src .) + +set(UT_SOURCES + src/AssertException.cpp + src/CompositeTestReporter.cpp + src/CurrentTest.cpp + src/DeferredTestReporter.cpp + src/DeferredTestResult.cpp + src/GlobalSettings.cpp + src/MemoryOutStream.cpp + src/ReportAssert.cpp + src/Test.cpp + src/TestDetails.cpp + src/TestList.cpp + src/TestReporter.cpp + src/TestReporterStdout.cpp + src/TestResults.cpp + src/TestRunner.cpp + src/XmlTestReporter.cpp + ) + +set(TEST_SOURCES + src/tests/TestAssertHandler.cpp + src/tests/TestCheckMacros.cpp + src/tests/TestChecks.cpp + src/tests/TestCompositeTestReporter.cpp + src/tests/TestCurrentTest.cpp + src/tests/TestDeferredTestReporter.cpp + src/tests/TestMemoryOutStream.cpp + src/tests/TestTest.cpp + src/tests/TestTestList.cpp + src/tests/TestTestMacros.cpp + src/tests/TestTestResults.cpp + src/tests/TestTestRunner.cpp + src/tests/TestTestSuite.cpp + src/tests/TestUnitTestPP.cpp + src/tests/TestXmlTestReporter.cpp + ) + +if(UNIX) + set(UT_SOURCES ${UT_SOURCES} + src/Posix/SignalTranslator.cpp + src/Posix/TimeHelpers.cpp + ) +endif() + +add_library(${LIB}unittestpp ${UT_SOURCES}) diff --git a/tests/framework/UnitTestpp/COPYING b/tests/framework/UnitTestpp/COPYING new file mode 100644 index 0000000..9f96308 --- /dev/null +++ b/tests/framework/UnitTestpp/COPYING @@ -0,0 +1,20 @@ +Copyright (c) 2006 Noel Llopis and Charles Nicholson + +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. diff --git a/tests/framework/UnitTestpp/ThirdPartyNotices.txt b/tests/framework/UnitTestpp/ThirdPartyNotices.txt new file mode 100644 index 0000000..0dacfc8 --- /dev/null +++ b/tests/framework/UnitTestpp/ThirdPartyNotices.txt @@ -0,0 +1,20 @@ +----------------- ThirdPartyNotices---------------------------------------------- + +This file is based on or incorporates material from the UnitTest++ r30 open source project.Microsoft is not the original author of this code but has modified it and is licensing the code under the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, whether by implication, estoppel or otherwise. + +UnitTest++ r30 + +Copyright (c) 2006 Noel Llopis and Charles Nicholson +Portions Copyright (c) Microsoft Corporation + +All Rights Reserved. + +MIT License + +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. + +-------------End of ThirdPartyNotices--------------------------------------- diff --git a/tests/framework/UnitTestpp/config.h b/tests/framework/UnitTestpp/config.h new file mode 100644 index 0000000..098bb53 --- /dev/null +++ b/tests/framework/UnitTestpp/config.h @@ -0,0 +1,86 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_CONFIG_H +#define UNITTEST_CONFIG_H + +// Standard defines documented here: http://predef.sourceforge.net + +#if defined(_MSC_VER) + #pragma warning(disable:4702) // unreachable code + #pragma warning(disable:4722) // destructor never returns, potential memory leak + + #if (_MSC_VER == 1200) // VC6 + #pragma warning(disable:4786) + #pragma warning(disable:4290) + #endif + + #ifdef _USRDLL + #define UNITTEST_WIN32_DLL + #endif + #define UNITTEST_WIN32 + #define _MS_WINDOWS +#endif + +#if defined(unix) || defined(__unix__) || defined(__unix) || defined(linux) || \ + defined(__APPLE__) || defined(__NetBSD__) || defined(__OpenBSD__) || defined(__FreeBSD__) + #define UNITTEST_POSIX +#endif + +#if defined(__MINGW32__) + #define UNITTEST_MINGW +#endif + + +// MemoryOutStream is a custom reimplementation of parts of std::ostringstream. +// Uncomment this line to have MemoryOutStream implemented in terms of std::ostringstream. +// This is useful if you are using the CHECK macros on objects that have something like this defined: +// std::ostringstream& operator<<(std::ostringstream& s, const YourObject& value) + +#define UNITTEST_MEMORYOUTSTREAM_IS_STD_OSTRINGSTREAM + + +// DeferredTestReporter uses the STL to collect test results for subsequent export by reporters like +// XmlTestReporter. If you don't want to use this functionality, uncomment this line and no STL +// headers or code will be compiled into UnitTest++ + +//#define UNITTEST_NO_DEFERRED_REPORTER + + +// By default, asserts that you report via UnitTest::ReportAssert() abort the current test and +// continue to the next one by throwing an exception, which unwinds the stack naturally, destroying +// all auto variables on its way back down. If you don't want to (or can't) use exceptions for your +// platform/compiler, uncomment this line. All exception code will be removed from UnitTest++, +// assert recovery will be done via setjmp/longjmp, and NO correct stack unwinding will happen! + +//#define UNITTEST_NO_EXCEPTIONS + +#endif diff --git a/tests/framework/UnitTestpp/src/AssertException.cpp b/tests/framework/UnitTestpp/src/AssertException.cpp new file mode 100644 index 0000000..715ebcb --- /dev/null +++ b/tests/framework/UnitTestpp/src/AssertException.cpp @@ -0,0 +1,48 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#ifndef UNITTEST_NO_EXCEPTIONS + +namespace UnitTest { + +AssertException::AssertException() +{ +} + +AssertException::~AssertException() throw() +{ +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/AssertException.h b/tests/framework/UnitTestpp/src/AssertException.h new file mode 100644 index 0000000..5178751 --- /dev/null +++ b/tests/framework/UnitTestpp/src/AssertException.h @@ -0,0 +1,54 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_ASSERTEXCEPTION_H +#define UNITTEST_ASSERTEXCEPTION_H + +#include "../config.h" +#ifndef UNITTEST_NO_EXCEPTIONS + +#include "HelperMacros.h" +#include + +namespace UnitTest { + +class AssertException : public std::exception +{ +public: + UNITTEST_LINKAGE AssertException(); + UNITTEST_LINKAGE virtual ~AssertException() throw(); +}; + +} + +#endif + +#endif diff --git a/tests/framework/UnitTestpp/src/CheckMacros.h b/tests/framework/UnitTestpp/src/CheckMacros.h new file mode 100644 index 0000000..92ccca0 --- /dev/null +++ b/tests/framework/UnitTestpp/src/CheckMacros.h @@ -0,0 +1,225 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_CHECKMACROS_H +#define UNITTEST_CHECKMACROS_H + +#include + +#include "HelperMacros.h" +#include "ExceptionMacros.h" +#include "Checks.h" +#include "AssertException.h" +#include "MemoryOutStream.h" +#include "TestDetails.h" +#include "CurrentTest.h" +#include "ReportAssertImpl.h" + +#ifdef CHECK + #error UnitTest++ redefines CHECK +#endif + +#ifdef CHECK_EQUAL + #error UnitTest++ redefines CHECK_EQUAL +#endif + +#ifdef CHECK_CLOSE + #error UnitTest++ redefines CHECK_CLOSE +#endif + +#ifdef CHECK_ARRAY_EQUAL + #error UnitTest++ redefines CHECK_ARRAY_EQUAL +#endif + +#ifdef CHECK_ARRAY_CLOSE + #error UnitTest++ redefines CHECK_ARRAY_CLOSE +#endif + +#ifdef CHECK_ARRAY2D_CLOSE + #error UnitTest++ redefines CHECK_ARRAY2D_CLOSE +#endif + +#ifdef VERIFY_IS_TRUE + #error UnitTest++ redefines VERIFY_IS_TRUE +#endif + +#ifdef VERIFY_IS_FALSE + #error UnitTest++ redefines VERIFY_IS_FALSE +#endif + +#ifdef VERIFY_ARE_EQUAL + #error UnitTest++ redefines VERIFY_ARE_EQUAL +#endif + +#ifdef VERIFY_ARE_NOT_EQUAL + #error UnitTest++ redefines VERIFY_ARE_NOT_EQUAL +#endif + +#ifdef VERIFY_THROWS + #error UnitTest++ redefines VERIFY_THROWS +#endif + +#ifdef VERIFY_IS_NOT_NULL + #error UnitTest++ redefines VERIFY_IS_NOT_NULL +#endif + +#ifdef VERIFY_IS_NULL + #error UnitTest++ redefines VERIFY_IS_NULL +#endif + +#ifdef WIN32 +#define VERIFY_IS_TRUE(expression, ...) CHECK_EQUAL(true, expression, __VA_ARGS__) +#define VERIFY_IS_FALSE(expression, ...) CHECK_EQUAL(false, expression, __VA_ARGS__) +#define VERIFY_ARE_NOT_EQUAL(expected, actual, ...) CHECK_NOT_EQUAL(expected, actual, __VA_ARGS__) +#define VERIFY_ARE_EQUAL(expected, actual, ...) CHECK_EQUAL(expected, actual, __VA_ARGS__) +#else +#define VERIFY_IS_TRUE(expression, ...) CHECK_EQUAL(true, expression, ##__VA_ARGS__) +#define VERIFY_IS_FALSE(expression, ...) CHECK_EQUAL(false, expression, ##__VA_ARGS__) +#define VERIFY_ARE_NOT_EQUAL(expected, actual, ...) CHECK_NOT_EQUAL(expected, actual, ##__VA_ARGS__) +#define VERIFY_ARE_EQUAL(expected, actual, ...) CHECK_EQUAL(expected, actual, ##__VA_ARGS__) +#endif + +#define VERIFY_THROWS(expression, exception) CHECK_THROW(expression, exception) +#define VERIFY_IS_NOT_NULL(expression) CHECK_NOT_NULL(expression) +#define VERIFY_IS_NULL(expression) CHECK_NULL(expression) + +#define CHECK(value) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + if (!UnitTest::Check(value)) \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), #value); \ + UNITTEST_MULTILINE_MACRO_END + +#ifdef WIN32 + +#define CHECK_EQUAL(expected, actual, ...) \ + do { \ + UnitTest::CheckEqual(*UnitTest::CurrentTest::Results(), #expected, #actual, expected, actual, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), __VA_ARGS__); \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_NOT_EQUAL(expected, actual, ...) \ + do { \ + UnitTest::CheckNotEqual(*UnitTest::CurrentTest::Results(), #expected, #actual, expected, actual, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), __VA_ARGS__); \ + UNITTEST_MULTILINE_MACRO_END + +#else + +#define CHECK_EQUAL(expected, actual, ...) \ + do { \ + try \ + { \ + UnitTest::CheckEqual(*UnitTest::CurrentTest::Results(), #expected, #actual, expected, actual, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), ##__VA_ARGS__); \ + } \ + catch (const std::exception& ex) \ + { \ + std::cerr << ex.what() << std::endl; \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), \ + "Unhandled exception in CHECK_EQUAL(" #expected ", " #actual ") - details: "); \ + } \ + UT_CATCH_ALL \ + ({ \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), \ + "Unhandled exception in CHECK_EQUAL(" #expected ", " #actual ")"); \ + }) \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_NOT_EQUAL(expected, actual, ...) \ + do { \ + try \ + { \ + UnitTest::CheckNotEqual(*UnitTest::CurrentTest::Results(), #expected, #actual, expected, actual, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), ##__VA_ARGS__); \ + } \ + UT_CATCH_ALL \ + ({ \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), \ + "Unhandled exception in CHECK_NOT_EQUAL(" #expected ", " #actual ")"); \ + }) \ + UNITTEST_MULTILINE_MACRO_END +#endif + +#define CHECK_NULL(expression) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + UnitTest::CheckNull(*UnitTest::CurrentTest::Results(), #expression, expression, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__)); \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_NOT_NULL(expression) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + UnitTest::CheckNotNull(*UnitTest::CurrentTest::Results(), #expression, expression, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__)); \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_CLOSE(expected, actual, tolerance) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + UnitTest::CheckClose(*UnitTest::CurrentTest::Results(), expected, actual, tolerance, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__)); \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_ARRAY_EQUAL(expected, actual, count) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + UnitTest::CheckArrayEqual(*UnitTest::CurrentTest::Results(), expected, actual, count, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__)); \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_ARRAY_CLOSE(expected, actual, count, tolerance) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + UnitTest::CheckArrayClose(*UnitTest::CurrentTest::Results(), expected, actual, count, tolerance, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__)); \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_ARRAY2D_CLOSE(expected, actual, rows, columns, tolerance) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + UnitTest::CheckArray2DClose(*UnitTest::CurrentTest::Results(), expected, actual, rows, columns, tolerance, UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__)); \ + UNITTEST_MULTILINE_MACRO_END + + +// CHECK_THROW and CHECK_ASSERT only exist when UNITTEST_NO_EXCEPTIONS isn't defined (see config.h) +#ifndef UNITTEST_NO_EXCEPTIONS +#define CHECK_THROW(expression, ExpectedExceptionType) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + bool caught_ = false; \ + try { \ + try { \ + expression; \ + } catch(const std::exception & _exc) { \ + std::string _msg(_exc.what()); \ + VERIFY_IS_TRUE(_msg.size() > 0); \ + throw; \ + } \ + } \ + catch (ExpectedExceptionType const&) { caught_ = true; } \ + catch (...) {} \ + if (!caught_) \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(*UnitTest::CurrentTest::Details(), __LINE__), "Expected exception: \"" #ExpectedExceptionType "\" not thrown"); \ + UNITTEST_MULTILINE_MACRO_END + +#define CHECK_ASSERT(expression) \ + UNITTEST_MULTILINE_MACRO_BEGIN \ + UnitTest::Detail::ExpectAssert(true); \ + CHECK_THROW(expression, UnitTest::AssertException); \ + UnitTest::Detail::ExpectAssert(false); \ + UNITTEST_MULTILINE_MACRO_END +#endif +#endif diff --git a/tests/framework/UnitTestpp/src/Checks.h b/tests/framework/UnitTestpp/src/Checks.h new file mode 100644 index 0000000..2b98456 --- /dev/null +++ b/tests/framework/UnitTestpp/src/Checks.h @@ -0,0 +1,338 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_CHECKS_H +#define UNITTEST_CHECKS_H + +#include + +#include "config.h" + +#include "TestResults.h" +#include "MemoryOutStream.h" +#include +#include + +#ifndef WIN32 +#include +#endif + +namespace UnitTest { + +namespace details +{ + inline std::string utf16_to_utf8(const std::basic_string &w) + { +#ifdef WIN32 + std::string result; + size_t size; + wcstombs_s(&size, nullptr, 0, (const wchar_t *)w.c_str(), w.size()); + result.resize(size); + // integr0808: added cast + wcstombs_s(&size, &result[0], size, (const wchar_t *)w.c_str(), w.size()); + return result; +#else + return boost::locale::conv::utf_to_utf(w, boost::locale::conv::stop); +#endif + } + + typedef char yes; + typedef char (&no)[2]; + + struct anyx + { + template anyx(const T &); + }; + no operator << (const anyx &, const anyx &); + + template yes check(T const&); + no check(no); + + template + struct support_stream_write { + static StreamType & stream; + static T1 & x; + static T2 & y; + static const bool value = (sizeof(check(stream << x)) == sizeof(yes)) && (sizeof(check(stream << y)) == sizeof(yes)); + }; + + template + inline std::string BuildFailureStringWithStream(const char *expectedStr, const char *actualStr, const T1 &expected, const T2 &actual) + { + UnitTest::MemoryOutStream stream; + stream << " where " << expectedStr << "=" << expected << " and " << actualStr << "=" << actual; + return stream.GetText(); + } + + template + struct BuildFailureStringImpl + { + std::string BuildString(const char *, const char *, const T1 &, const T2 &) + { + // Don't do anything since operator<< isn't supported. + return ""; + } + }; + + template + struct BuildFailureStringImpl + { + std::string BuildString(const char *expectedStr, const char *actualStr, const T1 &expected, const T2 &actual) + { + return BuildFailureStringWithStream(expectedStr, actualStr, expected, actual); + } + }; + + template + inline std::string BuildFailureString(const char *expectedStr, const char *actualStr, const T1 &expected, const T2 &actual) + { + return BuildFailureStringImpl::value>().BuildString(expectedStr, actualStr, expected, actual); + } + inline std::string BuildFailureString(const char *expectedStr, const char *actualStr, const std::basic_string &expected, const std::basic_string &actual) + { + return BuildFailureStringWithStream(expectedStr, actualStr, utf16_to_utf8(expected), utf16_to_utf8(actual)); + } + inline std::string BuildFailureString(const char *expectedStr, const char *actualStr, const utf16char *expected, const utf16char *actual) + { + return BuildFailureStringWithStream(expectedStr, actualStr, utf16_to_utf8(expected), utf16_to_utf8(actual)); + } + inline std::string BuildFailureString(const char *expectedStr, const char *actualStr, const std::basic_string &expected, const utf16char *actual) + { + return BuildFailureStringWithStream(expectedStr, actualStr, utf16_to_utf8(expected), utf16_to_utf8(actual)); + } + inline std::string BuildFailureString(const char *expectedStr, const char *actualStr, const utf16char *expected, const std::basic_string &actual) + { + return BuildFailureStringWithStream(expectedStr, actualStr, utf16_to_utf8(expected), utf16_to_utf8(actual)); + } +} // namespace details + +template< typename Value > +bool Check(Value const value) +{ + return !!value; // doing double negative to avoid silly VS warnings +} + +#ifdef _MS_WINDOWS +#pragma warning(push) +#pragma warning(disable : 4389) +#endif +template +bool CheckEqualImpl(const Expected &expected, const Actual &actual) +{ + return !(expected == actual); +} +#ifdef _MS_WINDOWS +#pragma warning(pop) +#endif + +inline bool CheckEqualImpl(const char *expected, const char *actual) { return !(std::strcmp(expected, actual) == 0); } +inline bool CheckEqualImpl(char *expected, const char *actual) { return !(std::strcmp(expected, actual) == 0); } +inline bool CheckEqualImpl(const char *expected, char *actual) { return !(std::strcmp(expected, actual) == 0); } +inline bool CheckEqualImpl(char *expected, char *actual) { return !(std::strcmp(expected, actual) == 0); } +inline bool CheckEqualImpl(const wchar_t *expected, const wchar_t *actual) { return !(wcscmp(expected, actual) == 0); } +inline bool CheckEqualImpl(wchar_t *expected, const wchar_t *actual) { return !(wcscmp(expected, actual) == 0); } +inline bool CheckEqualImpl(const wchar_t *expected, wchar_t *actual) { return !(wcscmp(expected, actual) == 0); } +inline bool CheckEqualImpl(wchar_t *expected, wchar_t *actual) { return !(wcscmp(expected, actual) == 0); } + +template< typename Expected, typename Actual > +void CheckEqual(TestResults& results, const char *expectedStr, const char *actualStr, const Expected & expected, const Actual & actual, TestDetails const& details, const char *msg = nullptr) +{ + if (CheckEqualImpl(expected, actual)) + { + UnitTest::MemoryOutStream stream; + stream << "CHECK_EQUAL(" << expectedStr << ", " << actualStr << ")"; + stream << details::BuildFailureString(expectedStr, actualStr, expected, actual) << std::endl; + if(msg != nullptr) + { + stream << msg; + } + results.OnTestFailure(details, stream.GetText()); + } +} + +template +void CheckNotEqual(TestResults& results, const char *expectedStr, const char *actualStr, Expected const& expected, Actual const& actual, TestDetails const& details, const char *msg = nullptr) +{ + if(!CheckEqualImpl(expected, actual)) + { + UnitTest::MemoryOutStream stream; + stream << "CHECK_NOT_EQUAL(" << expectedStr << ", " << actualStr << ")"; + stream << details::BuildFailureString(expectedStr, actualStr, expected, actual) << std::endl; + if(msg != nullptr) + { + stream << msg; + } + results.OnTestFailure(details, stream.GetText()); + } +} + +template +void CheckNull(TestResults& results, const char *actualStr, Actual const& actual, TestDetails const& details) +{ + if(actual) + { + UnitTest::MemoryOutStream stream; + stream << "CHECK_NULL(" << actualStr << ")"; + results.OnTestFailure(details, stream.GetText()); + } +} + +template +void CheckNotNull(TestResults& results, const char *actualStr, Actual const& actual, TestDetails const& details) +{ + if(!actual) + { + UnitTest::MemoryOutStream stream; + stream << "CHECK_NOT_NULL(" << actualStr << ")"; + results.OnTestFailure(details, stream.GetText()); + } +} + +template< typename Expected, typename Actual, typename Tolerance > +bool AreClose(Expected const& expected, Actual const& actual, Tolerance const& tolerance) +{ + return (actual >= (expected - tolerance)) && (actual <= (expected + tolerance)); +} + +template< typename Expected, typename Actual, typename Tolerance > +void CheckClose(TestResults& results, Expected const& expected, Actual const& actual, Tolerance const& tolerance, + TestDetails const& details) +{ + if (!AreClose(expected, actual, tolerance)) + { + UnitTest::MemoryOutStream stream; + stream << "Expected " << expected << " +/- " << tolerance << " but was " << actual; + + results.OnTestFailure(details, stream.GetText()); + } +} + +template< typename Expected, typename Actual > +void CheckArrayEqual(TestResults& results, Expected const& expected, Actual const& actual, + int const count, TestDetails const& details) +{ + bool equal = true; + for (int i = 0; i < count; ++i) + equal &= (expected[i] == actual[i]); + + if (!equal) + { + UnitTest::MemoryOutStream stream; + + stream << "Expected [ "; + + for (int expectedIndex = 0; expectedIndex < count; ++expectedIndex) + stream << expected[expectedIndex] << " "; + + stream << "] but was [ "; + + for (int actualIndex = 0; actualIndex < count; ++actualIndex) + stream << actual[actualIndex] << " "; + + stream << "]"; + + results.OnTestFailure(details, stream.GetText()); + } +} + +template< typename Expected, typename Actual, typename Tolerance > +bool ArrayAreClose(Expected const& expected, Actual const& actual, int const count, Tolerance const& tolerance) +{ + bool equal = true; + for (int i = 0; i < count; ++i) + equal &= AreClose(expected[i], actual[i], tolerance); + return equal; +} + +template< typename Expected, typename Actual, typename Tolerance > +void CheckArrayClose(TestResults& results, Expected const& expected, Actual const& actual, + int const count, Tolerance const& tolerance, TestDetails const& details) +{ + bool equal = ArrayAreClose(expected, actual, count, tolerance); + + if (!equal) + { + UnitTest::MemoryOutStream stream; + + stream << "Expected [ "; + for (int expectedIndex = 0; expectedIndex < count; ++expectedIndex) + stream << expected[expectedIndex] << " "; + stream << "] +/- " << tolerance << " but was [ "; + + for (int actualIndex = 0; actualIndex < count; ++actualIndex) + stream << actual[actualIndex] << " "; + stream << "]"; + + results.OnTestFailure(details, stream.GetText()); + } +} + +template< typename Expected, typename Actual, typename Tolerance > +void CheckArray2DClose(TestResults& results, Expected const& expected, Actual const& actual, + int const rows, int const columns, Tolerance const& tolerance, TestDetails const& details) +{ + bool equal = true; + for (int i = 0; i < rows; ++i) + equal &= ArrayAreClose(expected[i], actual[i], columns, tolerance); + + if (!equal) + { + UnitTest::MemoryOutStream stream; + + stream << "Expected [ "; + + for (int expectedRow = 0; expectedRow < rows; ++expectedRow) + { + stream << "[ "; + for (int expectedColumn = 0; expectedColumn < columns; ++expectedColumn) + stream << expected[expectedRow][expectedColumn] << " "; + stream << "] "; + } + + stream << "] +/- " << tolerance << " but was [ "; + + for (int actualRow = 0; actualRow < rows; ++actualRow) + { + stream << "[ "; + for (int actualColumn = 0; actualColumn < columns; ++actualColumn) + stream << actual[actualRow][actualColumn] << " "; + stream << "] "; + } + + stream << "]"; + + results.OnTestFailure(details, stream.GetText()); + } +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/CompositeTestReporter.cpp b/tests/framework/UnitTestpp/src/CompositeTestReporter.cpp new file mode 100644 index 0000000..428892d --- /dev/null +++ b/tests/framework/UnitTestpp/src/CompositeTestReporter.cpp @@ -0,0 +1,104 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#include "CompositeTestReporter.h" + +namespace UnitTest { + +CompositeTestReporter::CompositeTestReporter() + : m_reporterCount(0) +{ +} + +int CompositeTestReporter::GetReporterCount() const +{ + return m_reporterCount; +} + +bool CompositeTestReporter::AddReporter(TestReporter* reporter) +{ + if (m_reporterCount == kMaxReporters) + return false; + +// Safe to ignore we check the size to make sure no buffer overruns before. +#pragma warning (push) +#pragma warning (disable : 6386) + m_reporters[m_reporterCount++] = reporter; +#pragma warning (pop) + + return true; +} + +bool CompositeTestReporter::RemoveReporter(TestReporter* reporter) +{ + for (int index = 0; index < m_reporterCount; ++index) + { + if (m_reporters[index] == reporter) + { + m_reporters[index] = m_reporters[m_reporterCount - 1]; + --m_reporterCount; + return true; + } + } + + return false; +} + +void CompositeTestReporter::ReportFailure(TestDetails const& details, char const* failure) +{ + for (int index = 0; index < m_reporterCount; ++index) + m_reporters[index]->ReportFailure(details, failure); +} + +void CompositeTestReporter::ReportTestStart(TestDetails const& test) +{ + for (int index = 0; index < m_reporterCount; ++index) + m_reporters[index]->ReportTestStart(test); +} + +void CompositeTestReporter::ReportTestFinish(TestDetails const& test, bool passed, float secondsElapsed) +{ + for (int index = 0; index < m_reporterCount; ++index) + m_reporters[index]->ReportTestFinish(test, passed, secondsElapsed); +} + +void CompositeTestReporter::ReportSummary(int totalTestCount, + int failedTestCount, + int failureCount, + float secondsElapsed) +{ + for (int index = 0; index < m_reporterCount; ++index) + m_reporters[index]->ReportSummary(totalTestCount, failedTestCount, failureCount, secondsElapsed); +} + +} diff --git a/tests/framework/UnitTestpp/src/CompositeTestReporter.h b/tests/framework/UnitTestpp/src/CompositeTestReporter.h new file mode 100644 index 0000000..abc51d1 --- /dev/null +++ b/tests/framework/UnitTestpp/src/CompositeTestReporter.h @@ -0,0 +1,65 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_COMPOSITETESTREPORTER_H +#define UNITTEST_COMPOSITETESTREPORTER_H + +#include "TestReporter.h" + +namespace UnitTest { + +class CompositeTestReporter : public TestReporter +{ +public: + UNITTEST_LINKAGE CompositeTestReporter(); + + UNITTEST_LINKAGE int GetReporterCount() const; + UNITTEST_LINKAGE bool AddReporter(TestReporter* reporter); + UNITTEST_LINKAGE bool RemoveReporter(TestReporter* reporter); + + UNITTEST_LINKAGE virtual void ReportTestStart(TestDetails const& test); + UNITTEST_LINKAGE virtual void ReportFailure(TestDetails const& test, char const* failure); + UNITTEST_LINKAGE virtual void ReportTestFinish(TestDetails const& test, bool passed, float secondsElapsed); + UNITTEST_LINKAGE virtual void ReportSummary(int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed); + +private: + enum { kMaxReporters = 16 }; + TestReporter* m_reporters[kMaxReporters]; + int m_reporterCount; + + // revoked + CompositeTestReporter(const CompositeTestReporter&); + CompositeTestReporter& operator =(const CompositeTestReporter&); +}; + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/CurrentTest.cpp b/tests/framework/UnitTestpp/src/CurrentTest.cpp new file mode 100644 index 0000000..daf3e6d --- /dev/null +++ b/tests/framework/UnitTestpp/src/CurrentTest.cpp @@ -0,0 +1,48 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +namespace UnitTest { + +UNITTEST_LINKAGE TestResults*& CurrentTest::Results() +{ + static TestResults* testResults = NULL; + return testResults; +} + +UNITTEST_LINKAGE const TestDetails*& CurrentTest::Details() +{ + static const TestDetails* testDetails = NULL; + return testDetails; +} + +} diff --git a/tests/framework/UnitTestpp/src/CurrentTest.h b/tests/framework/UnitTestpp/src/CurrentTest.h new file mode 100644 index 0000000..fdcd9c6 --- /dev/null +++ b/tests/framework/UnitTestpp/src/CurrentTest.h @@ -0,0 +1,50 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_CURRENTTESTRESULTS_H +#define UNITTEST_CURRENTTESTRESULTS_H + +#include "HelperMacros.h" + +namespace UnitTest { + +class TestResults; +class TestDetails; + +namespace CurrentTest +{ + UNITTEST_LINKAGE TestResults*& Results(); + UNITTEST_LINKAGE const TestDetails*& Details(); +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/DeferredTestReporter.cpp b/tests/framework/UnitTestpp/src/DeferredTestReporter.cpp new file mode 100644 index 0000000..170eff2 --- /dev/null +++ b/tests/framework/UnitTestpp/src/DeferredTestReporter.cpp @@ -0,0 +1,62 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +using namespace UnitTest; + +void DeferredTestReporter::ReportTestStart(TestDetails const& details) +{ + m_results.push_back(DeferredTestResult(details.suiteName, details.testName)); +} + +void DeferredTestReporter::ReportFailure(TestDetails const& details, char const* failure) +{ + DeferredTestResult& r = m_results.back(); + r.failed = true; + r.failures.push_back(DeferredTestFailure(details.lineNumber, failure)); + r.failureFile = details.filename; +} + +void DeferredTestReporter::ReportTestFinish(TestDetails const&, bool, float secondsElapsed) +{ + DeferredTestResult& r = m_results.back(); + r.timeElapsed = secondsElapsed; +} + +DeferredTestReporter::DeferredTestResultList& DeferredTestReporter::GetResults() +{ + return m_results; +} + +#endif diff --git a/tests/framework/UnitTestpp/src/DeferredTestReporter.h b/tests/framework/UnitTestpp/src/DeferredTestReporter.h new file mode 100644 index 0000000..4f40d9d --- /dev/null +++ b/tests/framework/UnitTestpp/src/DeferredTestReporter.h @@ -0,0 +1,64 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_DEFERREDTESTREPORTER_H +#define UNITTEST_DEFERREDTESTREPORTER_H + +#include "../config.h" + +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +#include "TestReporter.h" +#include "DeferredTestResult.h" + +#include + +namespace UnitTest +{ + +class DeferredTestReporter : public TestReporter +{ +public: + UNITTEST_LINKAGE virtual void ReportTestStart(TestDetails const& details); + UNITTEST_LINKAGE virtual void ReportFailure(TestDetails const& details, char const* failure); + UNITTEST_LINKAGE virtual void ReportTestFinish(TestDetails const& details, bool passed, float secondsElapsed); + + typedef std::vector< DeferredTestResult > DeferredTestResultList; + UNITTEST_LINKAGE DeferredTestResultList& GetResults(); + +private: + DeferredTestResultList m_results; +}; + +} + +#endif +#endif diff --git a/tests/framework/UnitTestpp/src/DeferredTestResult.cpp b/tests/framework/UnitTestpp/src/DeferredTestResult.cpp new file mode 100644 index 0000000..68856bb --- /dev/null +++ b/tests/framework/UnitTestpp/src/DeferredTestResult.cpp @@ -0,0 +1,82 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +#include "DeferredTestResult.h" + +namespace UnitTest +{ + +DeferredTestFailure::DeferredTestFailure() + : lineNumber(-1) +{ + failureStr[0] = '\0'; +} + +DeferredTestFailure::DeferredTestFailure(int lineNumber_, const char* failureStr_) + : lineNumber(lineNumber_) +{ +// Ignoring warning about possible overrun because we aren't going to entirely +// change how unittestpp deals with strings. +#pragma warning (push) +#pragma warning (disable : 6204) + std::strcpy(failureStr, failureStr_); +#pragma warning (pop) +} + +DeferredTestResult::DeferredTestResult() + : suiteName("") + , testName("") + , failureFile("") + , timeElapsed(0.0f) + , failed(false) +{ +} + +DeferredTestResult::DeferredTestResult(char const* suite, char const* test) + : suiteName(suite) + , testName(test) + , failureFile("") + , timeElapsed(0.0f) + , failed(false) +{ +} + +DeferredTestResult::~DeferredTestResult() +{ +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/DeferredTestResult.h b/tests/framework/UnitTestpp/src/DeferredTestResult.h new file mode 100644 index 0000000..8276e2d --- /dev/null +++ b/tests/framework/UnitTestpp/src/DeferredTestResult.h @@ -0,0 +1,81 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_DEFERREDTESTRESULT_H +#define UNITTEST_DEFERREDTESTRESULT_H + +#include "../config.h" +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +#include "HelperMacros.h" +#include +#include + +namespace UnitTest +{ + +class DeferredTestFailure +{ +public: + UNITTEST_LINKAGE DeferredTestFailure(); + UNITTEST_LINKAGE DeferredTestFailure(int lineNumber_, const char* failureStr_); + + int lineNumber; + char failureStr[1024]; +}; + +} + +namespace UnitTest +{ + +class DeferredTestResult +{ +public: + UNITTEST_LINKAGE DeferredTestResult(); + UNITTEST_LINKAGE DeferredTestResult(char const* suite, char const* test); + UNITTEST_LINKAGE ~DeferredTestResult(); + + std::string suiteName; + std::string testName; + std::string failureFile; + + typedef std::vector< DeferredTestFailure > FailureVec; + FailureVec failures; + + float timeElapsed; + bool failed; +}; + +} + +#endif +#endif diff --git a/tests/framework/UnitTestpp/src/ExceptionMacros.h b/tests/framework/UnitTestpp/src/ExceptionMacros.h new file mode 100644 index 0000000..1d92b32 --- /dev/null +++ b/tests/framework/UnitTestpp/src/ExceptionMacros.h @@ -0,0 +1,49 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_EXCEPTIONMACROS_H +#define UNITTEST_EXCEPTIONMACROS_H + +#include "../config.h" + +#ifndef UNITTEST_NO_EXCEPTIONS + #define UT_TRY(x) try x + #define UT_THROW(x) throw x + #define UT_CATCH(ExceptionType, ExceptionName, CatchBody) catch(ExceptionType& ExceptionName) CatchBody + #define UT_CATCH_ALL(CatchBody) catch(...) CatchBody +#else + #define UT_TRY(x) x + #define UT_THROW(x) + #define UT_CATCH(ExceptionType, ExceptionName, CatchBody) + #define UT_CATCH_ALL(CatchBody) +#endif + +#endif diff --git a/tests/framework/UnitTestpp/src/ExecuteTest.h b/tests/framework/UnitTestpp/src/ExecuteTest.h new file mode 100644 index 0000000..8cd5ed7 --- /dev/null +++ b/tests/framework/UnitTestpp/src/ExecuteTest.h @@ -0,0 +1,94 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_EXECUTE_TEST_H +#define UNITTEST_EXECUTE_TEST_H + +#include "../config.h" +#include "ExceptionMacros.h" +#include "TestDetails.h" +#include "MemoryOutStream.h" +#include "AssertException.h" +#include "CurrentTest.h" +#include "TestResults.h" + +#ifdef UNITTEST_NO_EXCEPTIONS + #include "ReportAssertImpl.h" +#endif + +#ifdef UNITTEST_POSIX + #include "Posix/SignalTranslator.h" +#endif + +#include + +namespace UnitTest { + +template< typename T > +void ExecuteTest(T& testObject, TestDetails const& details, bool isMockTest) +{ + if (isMockTest == false) + { + CurrentTest::Details() = &details; + } + +#ifdef UNITTEST_NO_EXCEPTIONS + if (UNITTEST_SET_ASSERT_JUMP_TARGET() == 0) + { +#endif +#ifndef UNITTEST_POSIX + UT_TRY({ testObject.RunImpl(); }) +#else + UT_TRY + ({ + UNITTEST_THROW_SIGNALS_POSIX_ONLY + testObject.RunImpl(); + }) +#endif + UT_CATCH(AssertException, e, { (void)e; }) + UT_CATCH(std::exception, e, + { + MemoryOutStream stream; + stream << "Unhandled exception: " << e.what(); + CurrentTest::Results()->OnTestFailure(details, stream.GetText()); + }) + UT_CATCH_ALL + ({ + CurrentTest::Results()->OnTestFailure(details, "Unhandled exception: test crashed"); + }) +#ifdef UNITTEST_NO_EXCEPTIONS + } +#endif +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/GlobalSettings.cpp b/tests/framework/UnitTestpp/src/GlobalSettings.cpp new file mode 100644 index 0000000..e694f99 --- /dev/null +++ b/tests/framework/UnitTestpp/src/GlobalSettings.cpp @@ -0,0 +1,69 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#include +#include +#include "GlobalSettings.h" + +namespace UnitTest { + +static std::string to_lower(const std::string &str) +{ + std::string retVal; + retVal.resize(str.size()); + std::transform(str.begin(), str.end(), retVal.begin(), ::tolower); + return retVal; +} + +std::map g_settings; + +void GlobalSettings::Add(const std::string &key, const std::string &value) +{ + g_settings[to_lower(key)] = value; +} + +bool GlobalSettings::Has(const std::string &key) +{ + return g_settings.find(to_lower(key)) != g_settings.end(); +} + +const std::string & GlobalSettings::Get(const std::string &key) +{ + if(!Has(key)) + { + throw std::invalid_argument("Error: property is not found"); + } + return g_settings.find(to_lower(key))->second; +} + +} \ No newline at end of file diff --git a/tests/framework/UnitTestpp/src/GlobalSettings.h b/tests/framework/UnitTestpp/src/GlobalSettings.h new file mode 100644 index 0000000..42e0084 --- /dev/null +++ b/tests/framework/UnitTestpp/src/GlobalSettings.h @@ -0,0 +1,59 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_GLOBAL_PROPERTIES_H +#define UNITTEST_GLOBAL_PROPERTIES_H + +#include + +namespace UnitTest { + + // Simple key value pairs for global properties. + // Any test case which specifies a 'Requires' TestProperty will only execute if + // the required property is satisfied as a key in the GlobalSettings. + class GlobalSettings + { + public: + + UNITTEST_LINKAGE static void Add(const std::string &key, const std::string &value); + + UNITTEST_LINKAGE static bool Has(const std::string &key); + + UNITTEST_LINKAGE static const std::string &Get(const std::string &key); + + private: + GlobalSettings(); + GlobalSettings(const GlobalSettings &); + GlobalSettings &operator=(const GlobalSettings &); + }; +} + +#endif diff --git a/tests/framework/UnitTestpp/src/HelperMacros.h b/tests/framework/UnitTestpp/src/HelperMacros.h new file mode 100644 index 0000000..512a71e --- /dev/null +++ b/tests/framework/UnitTestpp/src/HelperMacros.h @@ -0,0 +1,83 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_HELPERMACROS_H +#define UNITTEST_HELPERMACROS_H + +#include "../config.h" + +#define UNITTEST_MULTILINE_MACRO_BEGIN do { + +#ifdef UNITTEST_WIN32 + #define UNITTEST_MULTILINE_MACRO_END \ + } __pragma(warning(push)) __pragma(warning(disable:4127)) while (0) __pragma(warning(pop)) +#else + #define UNITTEST_MULTILINE_MACRO_END } while(0) +#endif + + +#ifdef UNITTEST_WIN32_DLL + #define UNITTEST_IMPORT __declspec(dllimport) + #define UNITTEST_EXPORT __declspec(dllexport) + + #ifdef UNITTEST_DLL_EXPORT + #define UNITTEST_LINKAGE UNITTEST_EXPORT + #define UNITTEST_IMPEXP_TEMPLATE + #else + #define UNITTEST_LINKAGE UNITTEST_IMPORT + #define UNITTEST_IMPEXP_TEMPLATE extern + #endif + + #define UNITTEST_STDVECTOR_LINKAGE(T) \ + __pragma(warning(push)) \ + __pragma(warning(disable:4231)) \ + UNITTEST_IMPEXP_TEMPLATE template class UNITTEST_LINKAGE std::allocator< T >; \ + UNITTEST_IMPEXP_TEMPLATE template class UNITTEST_LINKAGE std::vector< T >; \ + __pragma(warning(pop)) +#else + #define UNITTEST_IMPORT + #define UNITTEST_EXPORT + #define UNITTEST_LINKAGE + #define UNITTEST_IMPEXP_TEMPLATE + #define UNITTEST_STDVECTOR_LINKAGE(T) +#endif + +#ifdef UNITTEST_WIN32 + #define UNITTEST_JMPBUF jmp_buf + #define UNITTEST_SETJMP setjmp + #define UNITTEST_LONGJMP longjmp +#elif defined UNITTEST_POSIX + #define UNITTEST_JMPBUF std::jmp_buf + #define UNITTEST_SETJMP setjmp + #define UNITTEST_LONGJMP std::longjmp +#endif + +#endif diff --git a/tests/framework/UnitTestpp/src/MemoryOutStream.cpp b/tests/framework/UnitTestpp/src/MemoryOutStream.cpp new file mode 100644 index 0000000..35a75f0 --- /dev/null +++ b/tests/framework/UnitTestpp/src/MemoryOutStream.cpp @@ -0,0 +1,210 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#ifdef UNITTEST_MEMORYOUTSTREAM_IS_STD_OSTRINGSTREAM + +namespace UnitTest { + +MemoryOutStream::MemoryOutStream() {} + +MemoryOutStream::~MemoryOutStream() {} + +char const* MemoryOutStream::GetText() const +{ + m_text = this->str(); + return m_text.c_str(); +} + +void MemoryOutStream::Clear() +{ + this->str(std::string()); + m_text = this->str(); +} + +} + +#else + +namespace UnitTest { + +namespace { + +template +void FormatToStream(MemoryOutStream& stream, char const* format, ValueType const& value) +{ + using namespace std; + + char txt[32]; + sprintf(txt, format, value); + stream << txt; +} + +int RoundUpToMultipleOfPow2Number (int n, int pow2Number) +{ + return (n + (pow2Number - 1)) & ~(pow2Number - 1); +} + +} + + +MemoryOutStream::MemoryOutStream(int const size) + : m_capacity (0) + , m_buffer (0) + +{ + GrowBuffer(size); +} + +MemoryOutStream::~MemoryOutStream() +{ + delete [] m_buffer; +} + +void MemoryOutStream::Clear() +{ + m_buffer[0] = '\0'; +} + +char const* MemoryOutStream::GetText() const +{ + return m_buffer; +} + +MemoryOutStream& MemoryOutStream::operator <<(char const* txt) +{ + using namespace std; + + int const bytesLeft = m_capacity - (int)strlen(m_buffer); + int const bytesRequired = (int)strlen(txt) + 1; + + if (bytesRequired > bytesLeft) + { + int const requiredCapacity = bytesRequired + m_capacity - bytesLeft; + GrowBuffer(requiredCapacity); + } + + strcat(m_buffer, txt); + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(int const n) +{ + FormatToStream(*this, "%i", n); + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(long const n) +{ + FormatToStream(*this, "%li", n); + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(unsigned long const n) +{ + FormatToStream(*this, "%lu", n); + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(long long const n) +{ +#ifdef UNITTEST_WIN32 + FormatToStream(*this, "%I64d", n); +#else + FormatToStream(*this, "%lld", n); +#endif + + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(unsigned long long const n) +{ +#ifdef UNITTEST_WIN32 + FormatToStream(*this, "%I64u", n); +#else + FormatToStream(*this, "%llu", n); +#endif + + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(float const f) +{ + FormatToStream(*this, "%ff", f); + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(void const* p) +{ + FormatToStream(*this, "%p", p); + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(unsigned int const s) +{ + FormatToStream(*this, "%u", s); + return *this; +} + +MemoryOutStream& MemoryOutStream::operator <<(double const d) +{ + FormatToStream(*this, "%f", d); + return *this; +} + +int MemoryOutStream::GetCapacity() const +{ + return m_capacity; +} + + +void MemoryOutStream::GrowBuffer(int const desiredCapacity) +{ + int const newCapacity = RoundUpToMultipleOfPow2Number(desiredCapacity, GROW_CHUNK_SIZE); + + using namespace std; + + char* buffer = new char[newCapacity]; + if (m_buffer) + strcpy(buffer, m_buffer); + else + strcpy(buffer, ""); + + delete [] m_buffer; + m_buffer = buffer; + m_capacity = newCapacity; +} + +} + + +#endif diff --git a/tests/framework/UnitTestpp/src/MemoryOutStream.h b/tests/framework/UnitTestpp/src/MemoryOutStream.h new file mode 100644 index 0000000..640b486 --- /dev/null +++ b/tests/framework/UnitTestpp/src/MemoryOutStream.h @@ -0,0 +1,104 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_MEMORYOUTSTREAM_H +#define UNITTEST_MEMORYOUTSTREAM_H + +#include "../config.h" +#include "HelperMacros.h" + +#ifdef UNITTEST_MEMORYOUTSTREAM_IS_STD_OSTRINGSTREAM + +#include + +namespace UnitTest +{ + +class MemoryOutStream : public std::ostringstream +{ +public: + UNITTEST_LINKAGE MemoryOutStream(); + UNITTEST_LINKAGE ~MemoryOutStream(); + UNITTEST_LINKAGE void Clear(); + UNITTEST_LINKAGE char const* GetText() const; + +private: + MemoryOutStream(MemoryOutStream const&); + void operator =(MemoryOutStream const&); + + mutable std::string m_text; +}; + +} + +#else + +#include + +namespace UnitTest +{ + +class UNITTEST_LINKAGE MemoryOutStream +{ +public: + explicit MemoryOutStream(int const size = 256); + ~MemoryOutStream(); + + void Clear(); + char const* GetText() const; + + MemoryOutStream& operator <<(char const* txt); + MemoryOutStream& operator <<(int n); + MemoryOutStream& operator <<(long n); + MemoryOutStream& operator <<(long long n); + MemoryOutStream& operator <<(unsigned long n); + MemoryOutStream& operator <<(unsigned long long n); + MemoryOutStream& operator <<(float f); + MemoryOutStream& operator <<(double d); + MemoryOutStream& operator <<(void const* p); + MemoryOutStream& operator <<(unsigned int s); + + enum { GROW_CHUNK_SIZE = 32 }; + int GetCapacity() const; + +private: + void operator= (MemoryOutStream const&); + void GrowBuffer(int capacity); + + int m_capacity; + char* m_buffer; +}; + +} + +#endif + +#endif diff --git a/tests/framework/UnitTestpp/src/Posix/SignalTranslator.cpp b/tests/framework/UnitTestpp/src/Posix/SignalTranslator.cpp new file mode 100644 index 0000000..dce9e25 --- /dev/null +++ b/tests/framework/UnitTestpp/src/Posix/SignalTranslator.cpp @@ -0,0 +1,77 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "SignalTranslator.h" + +namespace UnitTest { + +sigjmp_buf* SignalTranslator::s_jumpTarget = 0; + +namespace { + +void SignalHandler(int sig) +{ + siglongjmp(*SignalTranslator::s_jumpTarget, sig ); +} + +} + + +SignalTranslator::SignalTranslator() +{ + m_oldJumpTarget = s_jumpTarget; + s_jumpTarget = &m_currentJumpTarget; + + struct sigaction action; + action.sa_flags = 0; + action.sa_handler = SignalHandler; + sigemptyset( &action.sa_mask ); + + sigaction( SIGSEGV, &action, &m_old_SIGSEGV_action ); + sigaction( SIGFPE , &action, &m_old_SIGFPE_action ); + sigaction( SIGTRAP, &action, &m_old_SIGTRAP_action ); + sigaction( SIGBUS , &action, &m_old_SIGBUS_action ); + sigaction( SIGILL , &action, &m_old_SIGBUS_action ); +} + +SignalTranslator::~SignalTranslator() +{ + sigaction( SIGILL , &m_old_SIGBUS_action , 0 ); + sigaction( SIGBUS , &m_old_SIGBUS_action , 0 ); + sigaction( SIGTRAP, &m_old_SIGTRAP_action, 0 ); + sigaction( SIGFPE , &m_old_SIGFPE_action , 0 ); + sigaction( SIGSEGV, &m_old_SIGSEGV_action, 0 ); + + s_jumpTarget = m_oldJumpTarget; +} + + +} diff --git a/tests/framework/UnitTestpp/src/Posix/SignalTranslator.h b/tests/framework/UnitTestpp/src/Posix/SignalTranslator.h new file mode 100644 index 0000000..163e5d8 --- /dev/null +++ b/tests/framework/UnitTestpp/src/Posix/SignalTranslator.h @@ -0,0 +1,73 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_SIGNALTRANSLATOR_H +#define UNITTEST_SIGNALTRANSLATOR_H + +#include +#include + +namespace UnitTest { + +class SignalTranslator +{ +public: + SignalTranslator(); + ~SignalTranslator(); + + static sigjmp_buf* s_jumpTarget; + +private: + sigjmp_buf m_currentJumpTarget; + sigjmp_buf* m_oldJumpTarget; + + struct sigaction m_old_SIGFPE_action; + struct sigaction m_old_SIGTRAP_action; + struct sigaction m_old_SIGSEGV_action; + struct sigaction m_old_SIGBUS_action; + struct sigaction m_old_SIGABRT_action; + struct sigaction m_old_SIGALRM_action; +}; + +#if !defined (__GNUC__) + #define UNITTEST_EXTENSION +#else + #define UNITTEST_EXTENSION __extension__ +#endif + +#define UNITTEST_THROW_SIGNALS_POSIX_ONLY \ + UnitTest::SignalTranslator sig; \ + if (UNITTEST_EXTENSION sigsetjmp(*UnitTest::SignalTranslator::s_jumpTarget, 1) != 0) \ + throw ("Unhandled system exception"); + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/Posix/TimeHelpers.cpp b/tests/framework/UnitTestpp/src/Posix/TimeHelpers.cpp new file mode 100644 index 0000000..d2fea38 --- /dev/null +++ b/tests/framework/UnitTestpp/src/Posix/TimeHelpers.cpp @@ -0,0 +1,64 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TimeHelpers.h" +#include + +namespace UnitTest { + +Timer::Timer() +{ + m_startTime.tv_sec = 0; + m_startTime.tv_usec = 0; +} + +void Timer::Start() +{ + gettimeofday(&m_startTime, 0); +} + +double Timer::GetTimeInMs() const +{ + struct timeval currentTime; + gettimeofday(¤tTime, 0); + + double const dsecs = currentTime.tv_sec - m_startTime.tv_sec; + double const dus = currentTime.tv_usec - m_startTime.tv_usec; + + return (dsecs * 1000.0) + (dus / 1000.0); +} + +void TimeHelpers::SleepMs(int ms) +{ + usleep(ms * 1000); +} + +} diff --git a/tests/framework/UnitTestpp/src/Posix/TimeHelpers.h b/tests/framework/UnitTestpp/src/Posix/TimeHelpers.h new file mode 100644 index 0000000..83c76ec --- /dev/null +++ b/tests/framework/UnitTestpp/src/Posix/TimeHelpers.h @@ -0,0 +1,59 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TIMEHELPERS_H +#define UNITTEST_TIMEHELPERS_H + +#include + +namespace UnitTest { + +class Timer +{ +public: + Timer(); + void Start(); + double GetTimeInMs() const; + +private: + struct timeval m_startTime; +}; + + +namespace TimeHelpers +{ + void SleepMs(int ms); +} + + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/ReportAssert.cpp b/tests/framework/UnitTestpp/src/ReportAssert.cpp new file mode 100644 index 0000000..499de0f --- /dev/null +++ b/tests/framework/UnitTestpp/src/ReportAssert.cpp @@ -0,0 +1,99 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#include "ReportAssert.h" +#include "ReportAssertImpl.h" + +#ifdef UNITTEST_NO_EXCEPTIONS + #include "ReportAssertImpl.h" +#endif + +namespace UnitTest { + +namespace +{ + bool& AssertExpectedFlag() + { + static bool s_assertExpected = false; + return s_assertExpected; + } +} + +UNITTEST_LINKAGE void ReportAssert(char const* description, char const* filename, int lineNumber) +{ + Detail::ReportAssertEx(CurrentTest::Results(), CurrentTest::Details(), + description, filename, lineNumber); +} + +namespace Detail { + +#ifdef UNITTEST_NO_EXCEPTIONS +UNITTEST_JMPBUF* GetAssertJmpBuf() +{ + static UNITTEST_JMPBUF s_jmpBuf; + return &s_jmpBuf; +} +#endif + +UNITTEST_LINKAGE void ReportAssertEx(TestResults* testResults, + const TestDetails* testDetails, + char const* description, + char const* filename, + int lineNumber) +{ + if (AssertExpectedFlag() == false) + { + TestDetails assertDetails(testDetails->testName, testDetails->suiteName, filename, lineNumber); + testResults->OnTestFailure(assertDetails, description); + } + + ExpectAssert(false); + +#ifndef UNITTEST_NO_EXCEPTIONS + throw AssertException(); +#else + UNITTEST_JUMP_TO_ASSERT_JUMP_TARGET(); +#endif +} + +UNITTEST_LINKAGE void ExpectAssert(bool expected) +{ + AssertExpectedFlag() = expected; +} + +UNITTEST_LINKAGE bool AssertExpected() +{ + return AssertExpectedFlag(); +} + +}} diff --git a/tests/framework/UnitTestpp/src/ReportAssert.h b/tests/framework/UnitTestpp/src/ReportAssert.h new file mode 100644 index 0000000..24c6098 --- /dev/null +++ b/tests/framework/UnitTestpp/src/ReportAssert.h @@ -0,0 +1,43 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_ASSERT_H +#define UNITTEST_ASSERT_H + +#include "HelperMacros.h" + +namespace UnitTest { + +UNITTEST_LINKAGE void ReportAssert(char const* description, char const* filename, int lineNumber); + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/ReportAssertImpl.h b/tests/framework/UnitTestpp/src/ReportAssertImpl.h new file mode 100644 index 0000000..4e08845 --- /dev/null +++ b/tests/framework/UnitTestpp/src/ReportAssertImpl.h @@ -0,0 +1,77 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_REPORTASSERTIMPL_H +#define UNITTEST_REPORTASSERTIMPL_H + +#include "../config.h" +#include "HelperMacros.h" + +#ifdef UNITTEST_NO_EXCEPTIONS + #include +#endif + +namespace UnitTest { + +class TestResults; +class TestDetails; + +namespace Detail { + +UNITTEST_LINKAGE void ExpectAssert(bool expected); + +UNITTEST_LINKAGE void ReportAssertEx(TestResults* testResults, + const TestDetails* testDetails, + char const* description, + char const* filename, + int lineNumber); + +UNITTEST_LINKAGE bool AssertExpected(); + +#ifdef UNITTEST_NO_EXCEPTIONS + UNITTEST_LINKAGE UNITTEST_JMPBUF* GetAssertJmpBuf(); + + #ifdef UNITTEST_WIN32 + #define UNITTEST_SET_ASSERT_JUMP_TARGET() \ + __pragma(warning(push)) __pragma(warning(disable:4611)) \ + UNITTEST_SETJMP(*UnitTest::Detail::GetAssertJmpBuf()) \ + __pragma(warning(pop)) + #else + #define UNITTEST_SET_ASSERT_JUMP_TARGET() UNITTEST_SETJMP(*UnitTest::Detail::GetAssertJmpBuf()) + #endif + + #define UNITTEST_JUMP_TO_ASSERT_JUMP_TARGET() UNITTEST_LONGJMP(*UnitTest::Detail::GetAssertJmpBuf(), 1) +#endif + +} +} + +#endif diff --git a/tests/framework/UnitTestpp/src/Test.cpp b/tests/framework/UnitTestpp/src/Test.cpp new file mode 100644 index 0000000..3c08a20 --- /dev/null +++ b/tests/framework/UnitTestpp/src/Test.cpp @@ -0,0 +1,62 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#include "ExecuteTest.h" + +#ifdef UNITTEST_POSIX + #include "Posix/SignalTranslator.h" +#endif + +namespace UnitTest { + +Test::Test(char const* testName, char const* suiteName, char const* filename, int lineNumber) + : m_details(testName, suiteName, filename, lineNumber) + , m_nextTest(0) + , m_isMockTest(false) +{ +} + +Test::~Test() +{ +} + +void Test::Run() +{ + ExecuteTest(*this, m_details, m_isMockTest); +} + +void Test::RunImpl() const +{ +} + +} diff --git a/tests/framework/UnitTestpp/src/Test.h b/tests/framework/UnitTestpp/src/Test.h new file mode 100644 index 0000000..0bd0a28 --- /dev/null +++ b/tests/framework/UnitTestpp/src/Test.h @@ -0,0 +1,65 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TEST_H +#define UNITTEST_TEST_H + +#include "TestDetails.h" +#include "TestProperties.h" + +namespace UnitTest { + +class TestResults; + +class Test +{ +public: + UNITTEST_LINKAGE explicit Test(char const* testName, char const* suiteName = "DefaultSuite", char const* filename = "", int lineNumber = 0); + UNITTEST_LINKAGE virtual ~Test(); + UNITTEST_LINKAGE void Run(); + + TestProperties m_properties; + + TestDetails const m_details; + Test* m_nextTest; + mutable bool m_isMockTest; + + UNITTEST_LINKAGE virtual void RunImpl() const; + +private: + Test(Test const&); + Test& operator =(Test const&); +}; + + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/TestDetails.cpp b/tests/framework/UnitTestpp/src/TestDetails.cpp new file mode 100644 index 0000000..25f1171 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestDetails.cpp @@ -0,0 +1,53 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +namespace UnitTest { + +TestDetails::TestDetails(char const* testName_, char const* suiteName_, char const* filename_, int lineNumber_) + : suiteName(suiteName_) + , testName(testName_) + , filename(filename_) + , lineNumber(lineNumber_) +{ +} + +TestDetails::TestDetails(const TestDetails& details, int lineNumber_) + : suiteName(details.suiteName) + , testName(details.testName) + , filename(details.filename) + , lineNumber(lineNumber_) +{ +} + + +} diff --git a/tests/framework/UnitTestpp/src/TestDetails.h b/tests/framework/UnitTestpp/src/TestDetails.h new file mode 100644 index 0000000..1036e5b --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestDetails.h @@ -0,0 +1,57 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTDETAILS_H +#define UNITTEST_TESTDETAILS_H + +#include "HelperMacros.h" + +namespace UnitTest { + +class TestDetails +{ +public: + UNITTEST_LINKAGE TestDetails(char const* testName, char const* suiteName, char const* filename, int lineNumber); + UNITTEST_LINKAGE TestDetails(const TestDetails& details, int lineNumber); + + char const* const suiteName; + char const* const testName; + char const* const filename; + int const lineNumber; + + UNITTEST_LINKAGE TestDetails(TestDetails const&); // Why is it public? --> http://gcc.gnu.org/bugs.html#cxx_rvalbind +private: + TestDetails& operator=(TestDetails const&); +}; + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/TestHeader.h b/tests/framework/UnitTestpp/src/TestHeader.h new file mode 100644 index 0000000..7835c99 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestHeader.h @@ -0,0 +1,56 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#pragma once + +#include "../config.h" + +#include "AssertException.h" +#include "CurrentTest.h" +#include "DeferredTestReporter.h" +#include "TestDetails.h" +#include "MemoryOutStream.h" +#include "TestResults.h" +#include "Test.h" +#include "TestList.h" +#include "TestReporter.h" +#include "TestReporterStdout.h" +#include "TimeHelpers.h" + +#include +#include +#include + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX +#include +#endif \ No newline at end of file diff --git a/tests/framework/UnitTestpp/src/TestList.cpp b/tests/framework/UnitTestpp/src/TestList.cpp new file mode 100644 index 0000000..8ef000c --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestList.cpp @@ -0,0 +1,107 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#include +#include + +namespace UnitTest { + +TestList::TestList() + : m_head(0) + , m_tail(0) +{ +} + +void TestList::Clear() +{ + m_head = 0; + m_tail = 0; +} + +void TestList::Add(Test* test) +{ + if (m_tail == 0) + { + assert(m_head == 0); + m_head = test; + m_tail = test; + } + else + { + m_tail->m_nextTest = test; + m_tail = test; + } +} + +Test* TestList::GetFirst() const +{ + return m_head; +} + +bool TestList::IsEmpty() const +{ + return m_head == nullptr; +} + +ListAdder::ListAdder(TestList& list, Test* test, ...) +{ + char * arg; + va_list argList; + va_start(argList, test); + for(arg = va_arg(argList, char*); arg != nullptr; arg = va_arg(argList, char*)) + { + char * key = arg; + arg = va_arg(argList, char*); + if(arg != nullptr) + { + char *value = arg; + test->m_properties.Add(key, value); + } + } + va_end(argList); + + // If on windows we could be either desktop or winrt. Make a requires property for the correct version. + // Only a desktop runner environment can execute a desktop test case and vice versa on winrt. + // This starts with visual studio versions after VS 2012. +#if defined(_MSC_VER) && (_MSC_VER >= 1800) +#ifdef __cplusplus_winrt + test->m_properties.Add("Requires", "winrt"); +#else + test->m_properties.Add("Requires", "desktop"); +#endif +#endif + + list.Add(test); +} + +} diff --git a/tests/framework/UnitTestpp/src/TestList.h b/tests/framework/UnitTestpp/src/TestList.h new file mode 100644 index 0000000..7cf5594 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestList.h @@ -0,0 +1,68 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTLIST_H +#define UNITTEST_TESTLIST_H + +#include "HelperMacros.h" + +namespace UnitTest { + +class Test; + +class TestList +{ +public: + UNITTEST_LINKAGE TestList(); + UNITTEST_LINKAGE void Add(Test* test); + + UNITTEST_LINKAGE Test* GetFirst() const; + + UNITTEST_LINKAGE bool IsEmpty() const; + + UNITTEST_LINKAGE void Clear(); + +private: + Test* m_head; + Test* m_tail; +}; + + +class UNITTEST_LINKAGE ListAdder +{ +public: + ListAdder(TestList& list, Test* test, ...); +}; + +} + + +#endif diff --git a/tests/framework/UnitTestpp/src/TestMacros.h b/tests/framework/UnitTestpp/src/TestMacros.h new file mode 100644 index 0000000..0e9f8b7 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestMacros.h @@ -0,0 +1,269 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTMACROS_H +#define UNITTEST_TESTMACROS_H + +#include "../config.h" +#include "TestSuite.h" +#include "TestList.h" +#include "ExceptionMacros.h" +#include "ExecuteTest.h" +#include "AssertException.h" +#include "TestDetails.h" +#include "MemoryOutStream.h" + +#ifndef UNITTEST_POSIX + #define UNITTEST_THROW_SIGNALS_POSIX_ONLY +#else + #include "Posix/SignalTranslator.h" +#endif + +#ifdef TEST + #error UnitTest++ redefines TEST +#endif + +#ifdef TEST_EX + #error UnitTest++ redefines TEST_EX +#endif + +#ifdef TEST_FIXTURE_EX + #error UnitTest++ redefines TEST_FIXTURE_EX +#endif + +#ifndef CREATED_GET_TEST_LIST +#define CREATED_GET_TEST_LIST + +#ifdef _MS_WINDOWS +#define _DLL_EXPORT __declspec(dllexport) +#elif __APPLE__ +#define _DLL_EXPORT __attribute__((visibility("default"))) +#else +#define _DLL_EXPORT +#endif + +namespace UnitTest +{ +#ifdef __APPLE__ + // + // The body of this function needs to be defined in exactly one CPP file in + // each unit test binary (shared object or DLL). + // + extern "C" + _DLL_EXPORT TestList& GetTestList(); + // This is what the implementation should look like in each instance: + // + // extern "C" UnitTest::TestList& UnitTest::GetTestList() + // { + // static TestList s_list; + // return s_list; + // } +#else + extern "C" + inline _DLL_EXPORT TestList& GetTestList() + { + static TestList s_list; + return s_list; + } +#endif +} +#endif + +#define SUITE(Name) \ + namespace Suite##Name { \ + namespace UnitTestSuite { \ + inline char const* GetSuiteName () { \ + return #Name ; \ + } \ + } \ + } \ + namespace Suite##Name + +#ifdef _MS_WINDOWS +#define TEST_EX(Name, List, ...) \ + class Test##Name : public UnitTest::Test \ + { \ + public: \ + Test##Name() : Test(#Name, UnitTestSuite::GetSuiteName(), __FILE__, __LINE__) {} \ + private: \ + virtual void RunImpl() const; \ + } test##Name##Instance; \ + \ + UnitTest::ListAdder adder##Name (List, &test##Name##Instance, __VA_ARGS__, NULL); \ + \ + void Test##Name::RunImpl() const + +#else +#define TEST_EX(Name, List, ...) \ + class Test##Name : public UnitTest::Test \ + { \ + public: \ + Test##Name() : Test(#Name, UnitTestSuite::GetSuiteName(), __FILE__, __LINE__) {} \ + private: \ + virtual void RunImpl() const; \ + } test##Name##Instance; \ + \ + UnitTest::ListAdder adder##Name (List, &test##Name##Instance, ##__VA_ARGS__, nullptr); \ + \ + void Test##Name::RunImpl() const +#endif + + +#ifdef _MS_WINDOWS +#define TEST(Name, ...) TEST_EX(Name, UnitTest::GetTestList(), __VA_ARGS__) +#else +#define TEST(Name, ...) TEST_EX(Name, UnitTest::GetTestList(), ##__VA_ARGS__) +#endif + +#ifdef _MS_WINDOWS +#define TEST_FIXTURE_EX(Fixture, Name, List, ...) \ + class Fixture##Name##Helper : public Fixture \ + { \ + public: \ + explicit Fixture##Name##Helper(UnitTest::TestDetails const& details) : m_details(details) {} \ + void RunImpl(); \ + UnitTest::TestDetails const& m_details; \ + private: \ + Fixture##Name##Helper(Fixture##Name##Helper const&); \ + Fixture##Name##Helper& operator =(Fixture##Name##Helper const&); \ + }; \ + \ + class Test##Fixture##Name : public UnitTest::Test \ + { \ + public: \ + Test##Fixture##Name() : Test(#Name, UnitTestSuite::GetSuiteName(), __FILE__, __LINE__) {} \ + private: \ + virtual void RunImpl() const; \ + } test##Fixture##Name##Instance; \ + \ + UnitTest::ListAdder adder##Fixture##Name (List, &test##Fixture##Name##Instance, __VA_ARGS__, NULL); \ + \ + void Test##Fixture##Name::RunImpl() const \ + { \ + volatile bool ctorOk = false; \ + UT_TRY \ + ({ \ + Fixture##Name##Helper fixtureHelper(m_details); \ + ctorOk = true; \ + UnitTest::ExecuteTest(fixtureHelper, m_details, false); \ + }) \ + UT_CATCH (UnitTest::AssertException, e, \ + { \ + (void)e; \ + }) \ + UT_CATCH (std::exception, e, \ + { \ + UnitTest::MemoryOutStream stream; \ + stream << "Unhandled exception: " << e.what(); \ + UnitTest::CurrentTest::Results()->OnTestFailure(m_details, stream.GetText()); \ + }) \ + UT_CATCH_ALL \ + ({ \ + if (ctorOk) \ + { \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(m_details, __LINE__), \ + "Unhandled exception while destroying fixture " #Fixture); \ + } \ + else \ + { \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(m_details, __LINE__), \ + "Unhandled exception while constructing fixture " #Fixture); \ + } \ + }) \ + } \ + void Fixture##Name##Helper::RunImpl() +#else +#define TEST_FIXTURE_EX(Fixture, Name, List, ...) \ + class Fixture##Name##Helper : public Fixture \ + { \ + public: \ + explicit Fixture##Name##Helper(UnitTest::TestDetails const& details) : m_details(details) {} \ + void RunImpl(); \ + UnitTest::TestDetails const& m_details; \ + private: \ + Fixture##Name##Helper(Fixture##Name##Helper const&); \ + Fixture##Name##Helper& operator =(Fixture##Name##Helper const&); \ + }; \ + \ + class Test##Fixture##Name : public UnitTest::Test \ + { \ + public: \ + Test##Fixture##Name() : Test(#Name, UnitTestSuite::GetSuiteName(), __FILE__, __LINE__) {} \ + private: \ + virtual void RunImpl() const; \ + } test##Fixture##Name##Instance; \ + \ + UnitTest::ListAdder adder##Fixture##Name (List, &test##Fixture##Name##Instance, ##__VA_ARGS__, NULL); \ + \ + void Test##Fixture##Name::RunImpl() const \ + { \ + volatile bool ctorOk = false; \ + UT_TRY \ + ({ \ + Fixture##Name##Helper fixtureHelper(m_details); \ + ctorOk = true; \ + UnitTest::ExecuteTest(fixtureHelper, m_details, false); \ + }) \ + UT_CATCH (UnitTest::AssertException, e, \ + { \ + (void)e; \ + }) \ + UT_CATCH (std::exception, e, \ + { \ + UnitTest::MemoryOutStream stream; \ + stream << "Unhandled exception: " << e.what(); \ + UnitTest::CurrentTest::Results()->OnTestFailure(m_details, stream.GetText()); \ + }) \ + UT_CATCH_ALL \ + ({ \ + if (ctorOk) \ + { \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(m_details, __LINE__), \ + "Unhandled exception while destroying fixture " #Fixture); \ + } \ + else \ + { \ + UnitTest::CurrentTest::Results()->OnTestFailure(UnitTest::TestDetails(m_details, __LINE__), \ + "Unhandled exception while constructing fixture " #Fixture); \ + } \ + }) \ + } \ + void Fixture##Name##Helper::RunImpl() +#endif + +#ifdef _MS_WINDOWS +#define TEST_FIXTURE(Fixture, Name, ...) TEST_FIXTURE_EX(Fixture, Name, UnitTest::GetTestList(), __VA_ARGS__) +#else +#define TEST_FIXTURE(Fixture, Name, ...) TEST_FIXTURE_EX(Fixture, Name, UnitTest::GetTestList(), ##__VA_ARGS__) +#endif + + +#endif diff --git a/tests/framework/UnitTestpp/src/TestProperties.h b/tests/framework/UnitTestpp/src/TestProperties.h new file mode 100644 index 0000000..9f731d1 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestProperties.h @@ -0,0 +1,98 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TEST_PROPERTIES_H +#define UNITTEST_TEST_PROPERTIES_H + +#include +#include +#include + +namespace UnitTest +{ + // Simple key value pairs. + class TestProperties + { + public: + + TestProperties() {} + + void Add(const std::string &key, const std::string &value) + { + if( !Has( key ) ) + { + m_properties[key] = value; + } + else + { + m_properties[key] += ";"; + m_properties[key] += value; + } + } + + bool Has(const std::string &key) const + { + return m_properties.find(key) != m_properties.end(); + } + + const std::string &Get(const std::string &key) const + { + if(!Has(key)) + { + throw std::invalid_argument("Error: property is not found"); + } + return m_properties.find(key)->second; + } + + const std::string &operator[](const std::string &key) const + { + return Get(key); + } + + std::map::const_iterator begin() const + { + return m_properties.begin(); + } + + std::map::const_iterator end() const + { + return m_properties.end(); + } + + private: + std::map m_properties; + TestProperties(const TestProperties &); + TestProperties &operator=(const TestProperties &); + }; + +} // namespace UnitTest + +#endif diff --git a/tests/framework/UnitTestpp/src/TestReporter.cpp b/tests/framework/UnitTestpp/src/TestReporter.cpp new file mode 100644 index 0000000..0823576 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestReporter.cpp @@ -0,0 +1,44 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +namespace UnitTest { + +TestReporter::TestReporter() +{ +} + +TestReporter::~TestReporter() +{ +} + +} diff --git a/tests/framework/UnitTestpp/src/TestReporter.h b/tests/framework/UnitTestpp/src/TestReporter.h new file mode 100644 index 0000000..f363764 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestReporter.h @@ -0,0 +1,54 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTREPORTER_H +#define UNITTEST_TESTREPORTER_H + +#include "HelperMacros.h" + +namespace UnitTest { + +class TestDetails; + +class TestReporter +{ +public: + UNITTEST_LINKAGE TestReporter(); + UNITTEST_LINKAGE virtual ~TestReporter(); + + UNITTEST_LINKAGE virtual void ReportTestStart(TestDetails const& test) = 0; + UNITTEST_LINKAGE virtual void ReportFailure(TestDetails const& test, char const* failure) = 0; + UNITTEST_LINKAGE virtual void ReportTestFinish(TestDetails const& test, bool passed, float secondsElapsed) = 0; + UNITTEST_LINKAGE virtual void ReportSummary(int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed) = 0; +}; + +} +#endif diff --git a/tests/framework/UnitTestpp/src/TestReporterStdout.cpp b/tests/framework/UnitTestpp/src/TestReporterStdout.cpp new file mode 100644 index 0000000..01a26eb --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestReporterStdout.cpp @@ -0,0 +1,143 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" +#include +#include +#include + +// cstdio doesn't pull in namespace std on VC6, so we do it here. +#if defined(UNITTEST_WIN32) && (_MSC_VER == 1200) + namespace std {} +#endif + +namespace UnitTest { + +// Function to work around outputing to the console when under WinRT. +static void PrintfWrapper(const char* format, ...) +{ + va_list args; + va_start(args, format); + +#ifdef __cplusplus_winrt + const auto bufSize = _vscprintf(format, args) + 1; // add 1 for null termination + std::vector byteArray; + byteArray.resize(bufSize); + vsnprintf_s(&byteArray[0], bufSize, bufSize, format, args); + + DWORD bytesWritten; + HANDLE h = GetStdHandle(STD_OUTPUT_HANDLE); + WriteFile(h, &byteArray[0], (DWORD)bufSize, &bytesWritten, NULL); +#else +#ifdef _MS_WINDOWS + vfprintf_s(stdout, format, args); +#else + vfprintf(stdout, format, args); +#endif +#endif + + va_end(args); +} + +static void ChangeConsoleTextColorToRed() +{ +#ifdef _MS_WINDOWS + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0004 | 0x0008); +#else + std::cout << "\033[1;31m"; +#endif +} + +static void ChangeConsoleTextColorToGreen() +{ +#ifdef _MS_WINDOWS + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), 0x0002 | 0x0008); +#else + std::cout << "\033[1;32m"; +#endif +} + +static void ChangeConsoleTextColorToGrey() +{ +#ifdef _MS_WINDOWS + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), FOREGROUND_RED | FOREGROUND_BLUE | FOREGROUND_GREEN); +#else + std::cout << "\033[0m"; +#endif +} + +void TestReporterStdout::ReportFailure(TestDetails const& details, char const* failure) +{ +#if defined(__APPLE__) || defined(__GNUG__) + char const* const errorFormat = "%s:%d: error: Failure in %s: %s FAILED\n"; +#else + char const* const errorFormat = "%s(%d): error: Failure in %s: %s FAILED\n"; +#endif + + ChangeConsoleTextColorToRed(); + PrintfWrapper(errorFormat, details.filename, details.lineNumber, details.testName, failure); + ChangeConsoleTextColorToGrey(); + std::fflush(stdout); +} + +void TestReporterStdout::ReportTestStart(TestDetails const& test) +{ + const char * format = "Starting test case %s:%s...\n"; + PrintfWrapper(format, test.suiteName, test.testName); + std::fflush(stdout); +} + +void TestReporterStdout::ReportTestFinish(TestDetails const& test, bool passed, float) +{ + if(passed) + { + const char * format = "Test case %s:%s "; + PrintfWrapper(format, test.suiteName, test.testName); + ChangeConsoleTextColorToGreen(); + PrintfWrapper("PASSED\n"); + ChangeConsoleTextColorToGrey(); + } + else + { + ChangeConsoleTextColorToRed(); + const char * format = "Test case %s:%s FAILED\n"; + PrintfWrapper(format, test.suiteName, test.testName); + ChangeConsoleTextColorToGrey(); + } + std::fflush(stdout); +} + +void TestReporterStdout::ReportSummary(int const, int const, + int const, float) +{ +} + +} diff --git a/tests/framework/UnitTestpp/src/TestReporterStdout.h b/tests/framework/UnitTestpp/src/TestReporterStdout.h new file mode 100644 index 0000000..253306b --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestReporterStdout.h @@ -0,0 +1,50 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTREPORTERSTDOUT_H +#define UNITTEST_TESTREPORTERSTDOUT_H + +#include "TestReporter.h" + +namespace UnitTest { + +class TestReporterStdout : public TestReporter +{ +private: + UNITTEST_LINKAGE virtual void ReportTestStart(TestDetails const& test); + UNITTEST_LINKAGE virtual void ReportFailure(TestDetails const& test, char const* failure); + UNITTEST_LINKAGE virtual void ReportTestFinish(TestDetails const& test, bool passed, float secondsElapsed); + UNITTEST_LINKAGE virtual void ReportSummary(int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed); +}; + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/TestResults.cpp b/tests/framework/UnitTestpp/src/TestResults.cpp new file mode 100644 index 0000000..a2b0dcd --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestResults.cpp @@ -0,0 +1,113 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#ifndef WIN32 +#include "signal.h" +#endif + +namespace UnitTest { + +TestResults::TestResults(TestReporter* testReporter, bool breakOnError) + : m_testReporter(testReporter) + , m_totalTestCount(0) + , m_failedTestCount(0) + , m_failureCount(0) + , m_currentTestFailed(false) + , m_breakOnError(breakOnError) +{ +} + +void TestResults::OnTestStart(TestDetails const& test) +{ + ++m_totalTestCount; + m_currentTestFailed = false; + if (m_testReporter) + m_testReporter->ReportTestStart(test); +} + +#ifdef WIN32 +#define DEBUG_BREAK() __debugbreak() +#else +#define DEBUG_BREAK() raise(SIGTRAP) +#endif + +void TestResults::OnTestFailure(TestDetails const& test, char const* failure) +{ + ++m_failureCount; + if (!m_currentTestFailed) + { + ++m_failedTestCount; + std::string fullTestName(test.suiteName); + fullTestName.append(":"); + fullTestName.append(test.testName); + m_failedTests.push_back(fullTestName); + m_currentTestFailed = true; + } + + if (m_testReporter) + { + m_testReporter->ReportFailure(test, failure); + if(m_breakOnError) + { + DEBUG_BREAK(); + } + } +} + +void TestResults::OnTestFinish(TestDetails const& test, float secondsElapsed) +{ + if (m_testReporter) + m_testReporter->ReportTestFinish(test, !m_currentTestFailed, secondsElapsed); +} + +int TestResults::GetTotalTestCount() const +{ + return m_totalTestCount; +} + +int TestResults::GetFailedTestCount() const +{ + return m_failedTestCount; +} + +int TestResults::GetFailureCount() const +{ + return m_failureCount; +} + +const std::vector & TestResults::GetFailedTests() const +{ + return m_failedTests; +} + +} diff --git a/tests/framework/UnitTestpp/src/TestResults.h b/tests/framework/UnitTestpp/src/TestResults.h new file mode 100644 index 0000000..01a6dd9 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestResults.h @@ -0,0 +1,76 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTRESULTS_H +#define UNITTEST_TESTRESULTS_H + +#include "HelperMacros.h" +#include +#include + +namespace UnitTest { + +class TestReporter; +class TestDetails; + +class TestResults +{ +public: + UNITTEST_LINKAGE explicit TestResults(TestReporter* reporter = 0, bool breakOnError = false); + + UNITTEST_LINKAGE void OnTestStart(TestDetails const& test); + UNITTEST_LINKAGE void OnTestFailure(TestDetails const& test, char const* failure); + UNITTEST_LINKAGE void OnTestFinish(TestDetails const& test, float secondsElapsed); + + UNITTEST_LINKAGE int GetTotalTestCount() const; + UNITTEST_LINKAGE int GetFailedTestCount() const; + UNITTEST_LINKAGE int GetFailureCount() const; + + UNITTEST_LINKAGE const std::vector & GetFailedTests() const; + +private: + TestReporter* m_testReporter; + int m_totalTestCount; + int m_failedTestCount; + int m_failureCount; + + bool m_currentTestFailed; + const bool m_breakOnError; + + std::vector m_failedTests; + + TestResults(TestResults const&); + TestResults& operator =(TestResults const&); +}; + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/TestRunner.cpp b/tests/framework/UnitTestpp/src/TestRunner.cpp new file mode 100644 index 0000000..3e1557a --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestRunner.cpp @@ -0,0 +1,183 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#include "TestMacros.h" +#include "TestRunner.h" + +#if _MSC_VER == 1600 +#include +#include +#else +#include +#endif + +namespace UnitTest { + +TestRunner::TestRunner(TestReporter& reporter, bool breakOnError) + : m_reporter(&reporter) + , m_result(new TestResults(&reporter, breakOnError)) + , m_timer(new Timer) +{ + m_timer->Start(); +} + +TestRunner::~TestRunner() +{ + delete m_result; + delete m_timer; +} + +TestResults* TestRunner::GetTestResults() +{ + return m_result; +} + +int TestRunner::Finish() const +{ + float const secondsElapsed = static_cast(m_timer->GetTimeInMs() / 1000.0); + m_reporter->ReportSummary(m_result->GetTotalTestCount(), + m_result->GetFailedTestCount(), + m_result->GetFailureCount(), + secondsElapsed); + + return m_result->GetFailureCount(); +} + +bool TestRunner::IsTestInSuite(const Test* const curTest, char const* suiteName) const +{ + using namespace std; + return (suiteName == NULL) || !strcmp(curTest->m_details.suiteName, suiteName); +} + +#if _MSC_VER == 1600 +// std::future and std::thread doesn't exist on Visual Studio 2010 so fall back +// to use agent. +class TestRunnerAgent : public Concurrency::agent +{ +public: + TestRunnerAgent(std::tr1::function func) : m_func(func) {} +protected: + void run() + { + Concurrency::Context::Oversubscribe(true); + m_func(); + Concurrency::Context::Oversubscribe(false); + done(); + } +private: + std::tr1::function m_func; +}; +#endif + +// Logic to decide the timeout for individual test +// 1. If /testtimeout is specified with testrunner arguments, use that timeout. +// 2. Else, if the test has a Timeout property set, use that timeout. +// 3. If both the above properties are not specified, use the default timeout value. +int TestRunner::GetTestTimeout(Test* const curTest, int const defaultTestTimeInMs) const +{ + std::stringstream timeoutstream; + int timeout = defaultTestTimeInMs; + if(UnitTest::GlobalSettings::Has("testtimeout")) + { + timeoutstream << UnitTest::GlobalSettings::Get("testtimeout"); + timeoutstream >> timeout; + } + else if(curTest->m_properties.Has("Timeout")) + { + timeoutstream << curTest->m_properties.Get("Timeout"); + timeoutstream >> timeout; + } + return timeout; +} + +void TestRunner::RunTest(TestResults* const result, Test* const curTest, int const defaultTestTimeInMs) const +{ + if (curTest->m_isMockTest == false) + CurrentTest::Results() = result; + + int maxTestTimeInMs = GetTestTimeout(curTest, defaultTestTimeInMs); + + Timer testTimer; + testTimer.Start(); + + result->OnTestStart(curTest->m_details); + + if(maxTestTimeInMs > 0) + { + bool timedOut = false; +#if _MSC_VER == 1600 + TestRunnerAgent testRunnerAgent([&]() + { + curTest->Run(); + }); + testRunnerAgent.start(); + try + { + Concurrency::agent::wait(&testRunnerAgent, maxTestTimeInMs); + } + catch(const Concurrency::operation_timed_out &) + { + timedOut = true; + } +#else + // Timed wait requires async execution. + auto testRunnerFuture = std::async(std::launch::async, [&]() + { + curTest->Run(); + }); + std::chrono::system_clock::time_point totalTime = std::chrono::system_clock::now() + std::chrono::milliseconds(maxTestTimeInMs); + if(testRunnerFuture.wait_until(totalTime) == std::future_status::timeout) + { + timedOut = true; + } +#endif + if(timedOut) + { + MemoryOutStream stream; + stream << "Test case timed out and is hung. Aborting all remaining test cases. "; + stream << "Expected under " << maxTestTimeInMs << "ms."; + result->OnTestFailure(curTest->m_details, stream.GetText()); + + abort(); + } + } + else + { + curTest->Run(); + } + + double const testTimeInMs = testTimer.GetTimeInMs(); + result->OnTestFinish(curTest->m_details, static_cast< float >(testTimeInMs / 1000.0)); +} + +} diff --git a/tests/framework/UnitTestpp/src/TestRunner.h b/tests/framework/UnitTestpp/src/TestRunner.h new file mode 100644 index 0000000..f275f6b --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestRunner.h @@ -0,0 +1,108 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTRUNNER_H +#define UNITTEST_TESTRUNNER_H + +#include "Test.h" +#include "TestList.h" +#include "CurrentTest.h" +#include "GlobalSettings.h" + +namespace UnitTest { + +class TestReporter; +class TestResults; +class Timer; + +struct True +{ + bool operator()(const Test* const) const + { + return true; + } +}; + +class TestRunner +{ +public: + UNITTEST_LINKAGE explicit TestRunner(TestReporter& reporter, bool breakOnError = false); + UNITTEST_LINKAGE ~TestRunner(); + + template + int RunTestsIf(TestList const& list, const Predicate& predicate, int defaultTestTimeInMs) const + { + return RunTestsIf(list, nullptr, predicate, defaultTestTimeInMs); + } + + template + int RunTestsIf(TestList const& list, char const* suiteName, + const Predicate& predicate, int defaultTestTimeInMs) const + { + Test* curTest = list.GetFirst(); + + while (curTest != 0) + { + if (IsTestInSuite(curTest, suiteName) && predicate(curTest)) + RunTest(m_result, curTest, defaultTestTimeInMs); + + curTest = curTest->m_nextTest; + } + + return Finish(); + } + + int RunTests(TestList const& list, char const* suiteName, int defaultTestTimeInMs) const + { + return RunTestsIf(list, suiteName, True(), defaultTestTimeInMs); + } + int RunTests(TestList const& list, int defaultTestTimeInMs) const + { + return RunTestsIf(list, nullptr, True(), defaultTestTimeInMs); + } + + UNITTEST_LINKAGE TestResults* GetTestResults(); + +private: + TestReporter* m_reporter; + TestResults* m_result; + Timer* m_timer; + + int GetTestTimeout(Test* const curTest, int const defaultTestTimeInMs) const; + + UNITTEST_LINKAGE int Finish() const; + UNITTEST_LINKAGE bool IsTestInSuite(const Test* const curTest, char const* suiteName) const; + UNITTEST_LINKAGE void RunTest(TestResults* const result, Test* const curTest, int const defaultTestTimeInMs) const; +}; + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/TestSuite.h b/tests/framework/UnitTestpp/src/TestSuite.h new file mode 100644 index 0000000..55aea6e --- /dev/null +++ b/tests/framework/UnitTestpp/src/TestSuite.h @@ -0,0 +1,43 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TESTSUITE_H +#define UNITTEST_TESTSUITE_H + +namespace UnitTestSuite +{ + inline char const* GetSuiteName () + { + return "DefaultSuite"; + } +} + +#endif diff --git a/tests/framework/UnitTestpp/src/TimeHelpers.h b/tests/framework/UnitTestpp/src/TimeHelpers.h new file mode 100644 index 0000000..ee93a81 --- /dev/null +++ b/tests/framework/UnitTestpp/src/TimeHelpers.h @@ -0,0 +1,38 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "../config.h" + +#if defined UNITTEST_POSIX + #include "Posix/TimeHelpers.h" +#else + #include "Win32/TimeHelpers.h" +#endif diff --git a/tests/framework/UnitTestpp/src/Win32/TimeHelpers.cpp b/tests/framework/UnitTestpp/src/Win32/TimeHelpers.cpp new file mode 100644 index 0000000..fbfa51c --- /dev/null +++ b/tests/framework/UnitTestpp/src/Win32/TimeHelpers.cpp @@ -0,0 +1,77 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +namespace UnitTest { + +Timer::Timer() + : m_threadHandle(::GetCurrentThread()) + , m_startTime(0) +{ +#if defined(UNITTEST_WIN32) && (_MSC_VER == 1200) // VC6 doesn't have DWORD_PTR + typedef unsigned long DWORD_PTR; +#endif + + DWORD_PTR systemMask; + ::GetProcessAffinityMask(GetCurrentProcess(), &m_processAffinityMask, &systemMask); + ::SetThreadAffinityMask(m_threadHandle, 1); + ::QueryPerformanceFrequency(reinterpret_cast< LARGE_INTEGER* >(&m_frequency)); + ::SetThreadAffinityMask(m_threadHandle, m_processAffinityMask); +} + +void Timer::Start() +{ + m_startTime = GetTime(); +} + +double Timer::GetTimeInMs() const +{ + __int64 const elapsedTime = GetTime() - m_startTime; + double const seconds = double(elapsedTime) / double(m_frequency); + return seconds * 1000.0; +} + +__int64 Timer::GetTime() const +{ + LARGE_INTEGER curTime; + ::SetThreadAffinityMask(m_threadHandle, 1); + ::QueryPerformanceCounter(&curTime); + ::SetThreadAffinityMask(m_threadHandle, m_processAffinityMask); + return curTime.QuadPart; +} + +void TimeHelpers::SleepMs(int ms) +{ + ::Sleep(ms); +} + +} diff --git a/tests/framework/UnitTestpp/src/Win32/TimeHelpers.h b/tests/framework/UnitTestpp/src/Win32/TimeHelpers.h new file mode 100644 index 0000000..c1b5b09 --- /dev/null +++ b/tests/framework/UnitTestpp/src/Win32/TimeHelpers.h @@ -0,0 +1,76 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_TIMEHELPERS_H +#define UNITTEST_TIMEHELPERS_H + +#include "../../config.h" +#include "../HelperMacros.h" + +#ifdef UNITTEST_MINGW + #ifndef __int64 + #define __int64 long long + #endif +#endif + +namespace UnitTest { + +class Timer +{ +public: + UNITTEST_LINKAGE Timer(); + UNITTEST_LINKAGE void Start(); + UNITTEST_LINKAGE double GetTimeInMs() const; + +private: + __int64 GetTime() const; + + void* m_threadHandle; + +#if defined(_WIN64) + unsigned __int64 m_processAffinityMask; +#else + unsigned long m_processAffinityMask; +#endif + + __int64 m_startTime; + __int64 m_frequency; +}; + + +namespace TimeHelpers +{ + UNITTEST_LINKAGE void SleepMs(int ms); +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/XmlTestReporter.cpp b/tests/framework/UnitTestpp/src/XmlTestReporter.cpp new file mode 100644 index 0000000..4e7dcc9 --- /dev/null +++ b/tests/framework/UnitTestpp/src/XmlTestReporter.cpp @@ -0,0 +1,162 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "TestHeader.h" + +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +#include "XmlTestReporter.h" + +#include +#include + +using std::string; +using std::ostringstream; +using std::ostream; + +namespace { + +void ReplaceChar(string& str, char c, string const& replacement) +{ + for (size_t pos = str.find(c); pos != string::npos; pos = str.find(c, pos + 1)) + str.replace(pos, 1, replacement); +} + +string XmlEscape(string const& value) +{ + string escaped = value; + + ReplaceChar(escaped, '&', "&"); + ReplaceChar(escaped, '<', "<"); + ReplaceChar(escaped, '>', ">"); + ReplaceChar(escaped, '\'', "'"); + ReplaceChar(escaped, '\"', """); + + return escaped; +} + +string BuildFailureMessage(string const& file, int line, string const& message) +{ + ostringstream failureMessage; + failureMessage << file << "(" << line << ") : " << message; + return failureMessage.str(); +} + +} + +namespace UnitTest { + +XmlTestReporter::XmlTestReporter(ostream& ostream) + : m_ostream(ostream) +{ +} + +void XmlTestReporter::ReportSummary(int totalTestCount, int failedTestCount, + int failureCount, float secondsElapsed) +{ + AddXmlElement(m_ostream, NULL); + + BeginResults(m_ostream, totalTestCount, failedTestCount, failureCount, secondsElapsed); + + DeferredTestResultList const& results = GetResults(); + for (DeferredTestResultList::const_iterator i = results.begin(); i != results.end(); ++i) + { + BeginTest(m_ostream, *i); + + if (i->failed) + AddFailure(m_ostream, *i); + + EndTest(m_ostream, *i); + } + + EndResults(m_ostream); +} + +void XmlTestReporter::AddXmlElement(ostream& os, char const* encoding) +{ + os << ""; +} + +void XmlTestReporter::BeginResults(std::ostream& os, int totalTestCount, int failedTestCount, + int failureCount, float secondsElapsed) +{ + os << ""; +} + +void XmlTestReporter::EndResults(std::ostream& os) +{ + os << ""; +} + +void XmlTestReporter::BeginTest(std::ostream& os, DeferredTestResult const& result) +{ + os << ""; + else + os << "/>"; +} + +void XmlTestReporter::AddFailure(std::ostream& os, DeferredTestResult const& result) +{ + os << ">"; // close element + + for (DeferredTestResult::FailureVec::const_iterator it = result.failures.begin(); + it != result.failures.end(); + ++it) + { + string const escapedMessage = XmlEscape(std::string(it->failureStr)); + string const message = BuildFailureMessage(result.failureFile, it->lineNumber, escapedMessage); + + os << ""; + } +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/XmlTestReporter.h b/tests/framework/UnitTestpp/src/XmlTestReporter.h new file mode 100644 index 0000000..54a5c2a --- /dev/null +++ b/tests/framework/UnitTestpp/src/XmlTestReporter.h @@ -0,0 +1,69 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_XMLTESTREPORTER_H +#define UNITTEST_XMLTESTREPORTER_H + +#include "../config.h" +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +#include "DeferredTestReporter.h" + +#include + +namespace UnitTest +{ + +class XmlTestReporter : public DeferredTestReporter +{ +public: + explicit UNITTEST_LINKAGE XmlTestReporter(std::ostream& ostream); + + virtual UNITTEST_LINKAGE void ReportSummary(int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed); + +private: + XmlTestReporter(XmlTestReporter const&); + XmlTestReporter& operator=(XmlTestReporter const&); + + void AddXmlElement(std::ostream& os, char const* encoding); + void BeginResults(std::ostream& os, int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed); + void EndResults(std::ostream& os); + void BeginTest(std::ostream& os, DeferredTestResult const& result); + void AddFailure(std::ostream& os, DeferredTestResult const& result); + void EndTest(std::ostream& os, DeferredTestResult const& result); + + std::ostream& m_ostream; +}; + +} + +#endif +#endif diff --git a/tests/framework/UnitTestpp/src/tests/RecordingReporter.h b/tests/framework/UnitTestpp/src/tests/RecordingReporter.h new file mode 100644 index 0000000..2f29a93 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/RecordingReporter.h @@ -0,0 +1,129 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_RECORDINGREPORTER_H +#define UNITTEST_RECORDINGREPORTER_H + +#include "../TestReporter.h" +#include + +#include "../TestDetails.h" + +struct RecordingReporter : public UnitTest::TestReporter +{ +private: + enum { kMaxStringLength = 256 }; + +public: + RecordingReporter() + : testRunCount(0) + , testFailedCount(0) + , lastFailedLine(0) + , testFinishedCount(0) + , lastFinishedTestTime(0) + , summaryTotalTestCount(0) + , summaryFailedTestCount(0) + , summaryFailureCount(0) + , summarySecondsElapsed(0) + { + lastStartedSuite[0] = '\0'; + lastStartedTest[0] = '\0'; + lastFailedFile[0] = '\0'; + lastFailedSuite[0] = '\0'; + lastFailedTest[0] = '\0'; + lastFailedMessage[0] = '\0'; + lastFinishedSuite[0] = '\0'; + lastFinishedTest[0] = '\0'; + } + + virtual void ReportTestStart(UnitTest::TestDetails const& test) + { + using namespace std; + + ++testRunCount; + strcpy(lastStartedSuite, test.suiteName); + strcpy(lastStartedTest, test.testName); + } + + virtual void ReportFailure(UnitTest::TestDetails const& test, char const* failure) + { + using namespace std; + + ++testFailedCount; + strcpy(lastFailedFile, test.filename); + lastFailedLine = test.lineNumber; + strcpy(lastFailedSuite, test.suiteName); + strcpy(lastFailedTest, test.testName); + strcpy(lastFailedMessage, failure); + } + + virtual void ReportTestFinish(UnitTest::TestDetails const& test, bool, float testDuration) + { + using namespace std; + + ++testFinishedCount; + strcpy(lastFinishedSuite, test.suiteName); + strcpy(lastFinishedTest, test.testName); + lastFinishedTestTime = testDuration; + } + + virtual void ReportSummary(int totalTestCount, int failedTestCount, int failureCount, float secondsElapsed) + { + summaryTotalTestCount = totalTestCount; + summaryFailedTestCount = failedTestCount; + summaryFailureCount = failureCount; + summarySecondsElapsed = secondsElapsed; + } + + int testRunCount; + char lastStartedSuite[kMaxStringLength]; + char lastStartedTest[kMaxStringLength]; + + int testFailedCount; + char lastFailedFile[kMaxStringLength]; + int lastFailedLine; + char lastFailedSuite[kMaxStringLength]; + char lastFailedTest[kMaxStringLength]; + char lastFailedMessage[kMaxStringLength]; + + int testFinishedCount; + char lastFinishedSuite[kMaxStringLength]; + char lastFinishedTest[kMaxStringLength]; + float lastFinishedTestTime; + + int summaryTotalTestCount; + int summaryFailedTestCount; + int summaryFailureCount; + float summarySecondsElapsed; +}; + + +#endif diff --git a/tests/framework/UnitTestpp/src/tests/ScopedCurrentTest.h b/tests/framework/UnitTestpp/src/tests/ScopedCurrentTest.h new file mode 100644 index 0000000..8130784 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/ScopedCurrentTest.h @@ -0,0 +1,68 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTEST_SCOPEDCURRENTTEST_H +#define UNITTEST_SCOPEDCURRENTTEST_H + +#include "../CurrentTest.h" +#include + +class ScopedCurrentTest +{ +public: + ScopedCurrentTest() + : m_oldTestResults(UnitTest::CurrentTest::Results()) + , m_oldTestDetails(UnitTest::CurrentTest::Details()) + { + } + + explicit ScopedCurrentTest(UnitTest::TestResults& newResults, const UnitTest::TestDetails* newDetails = NULL) + : m_oldTestResults(UnitTest::CurrentTest::Results()) + , m_oldTestDetails(UnitTest::CurrentTest::Details()) + { + UnitTest::CurrentTest::Results() = &newResults; + + if (newDetails != NULL) + UnitTest::CurrentTest::Details() = newDetails; + } + + ~ScopedCurrentTest() + { + UnitTest::CurrentTest::Results() = m_oldTestResults; + UnitTest::CurrentTest::Details() = m_oldTestDetails; + } + +private: + UnitTest::TestResults* m_oldTestResults; + const UnitTest::TestDetails* m_oldTestDetails; +}; + +#endif diff --git a/tests/framework/UnitTestpp/src/tests/TestAssertHandler.cpp b/tests/framework/UnitTestpp/src/tests/TestAssertHandler.cpp new file mode 100644 index 0000000..3f01f8f --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestAssertHandler.cpp @@ -0,0 +1,163 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +#include "../AssertException.h" + +#include + +using namespace UnitTest; + +namespace { + +TEST(CanSetAssertExpected) +{ + Detail::ExpectAssert(true); + CHECK(Detail::AssertExpected()); + + Detail::ExpectAssert(false); + CHECK(!Detail::AssertExpected()); +} + +#ifndef UNITTEST_NO_EXCEPTIONS + +TEST(ReportAssertThrowsAssertException) +{ + bool caught = false; + + try + { + TestResults testResults; + TestDetails testDetails("", "", "", 0); + Detail::ReportAssertEx(&testResults, &testDetails, "", "", 0); + } + catch(AssertException const&) + { + caught = true; + } + + CHECK(true == caught); +} + +TEST(ReportAssertClearsExpectAssertFlag) +{ + RecordingReporter reporter; + TestResults testResults(&reporter); + TestDetails testDetails("", "", "", 0); + + try + { + Detail::ExpectAssert(true); + Detail::ReportAssertEx(&testResults, &testDetails, "", "", 0); + } + catch(AssertException const&) + { + } + + CHECK(Detail::AssertExpected() == false); + CHECK_EQUAL(0, reporter.testFailedCount); +} + +TEST(ReportAssertWritesFailureToResultsAndDetailsWhenAssertIsNotExpected) +{ + const int lineNumber = 12345; + const char* description = "description"; + const char* filename = "filename"; + + RecordingReporter reporter; + TestResults testResults(&reporter); + TestDetails testDetails("", "", "", 0); + + try + { + Detail::ReportAssertEx(&testResults, &testDetails, description, filename, lineNumber); + } + catch(AssertException const&) + { + } + + CHECK_EQUAL(description, reporter.lastFailedMessage); + CHECK_EQUAL(filename, reporter.lastFailedFile); + CHECK_EQUAL(lineNumber, reporter.lastFailedLine); +} + +TEST(ReportAssertReportsNoErrorsWhenAssertIsExpected) +{ + Detail::ExpectAssert(true); + + RecordingReporter reporter; + TestResults testResults(&reporter); + TestDetails testDetails("", "", "", 0); + + try + { + Detail::ReportAssertEx(&testResults, &testDetails, "", "", 0); + } + catch(AssertException const&) + { + } + + CHECK_EQUAL(0, reporter.testFailedCount); +} + +TEST(CheckAssertMacroSetsAssertExpectationToFalseAfterRunning) +{ + Detail::ExpectAssert(true); + CHECK_ASSERT(ReportAssert("", "", 0)); + CHECK(!Detail::AssertExpected()); + Detail::ExpectAssert(false); +} + +#else + +TEST(SetAssertJumpTargetReturnsFalseWhenSettingJumpTarget) +{ + CHECK(UNITTEST_SET_ASSERT_JUMP_TARGET() == false); +} + +TEST(JumpToAssertJumpTarget_JumpsToSetPoint_ReturnsTrue) +{ + const volatile bool taken = !!UNITTEST_SET_ASSERT_JUMP_TARGET(); + + volatile bool set = false; + if (taken == false) + { + UNITTEST_JUMP_TO_ASSERT_JUMP_TARGET(); + set = true; + } + + CHECK(set == false); +} + +#endif + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestCheckMacros.cpp b/tests/framework/UnitTestpp/src/tests/TestCheckMacros.cpp new file mode 100644 index 0000000..2bdd2cc --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestCheckMacros.cpp @@ -0,0 +1,546 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +using namespace std; + +namespace { + +TEST(CheckSucceedsOnTrue) +{ + bool failure = true; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + + ScopedCurrentTest scopedResults(testResults); + CHECK(true); + + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(!failure); +} + +TEST(CheckFailsOnFalse) +{ + bool failure = false; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + CHECK(false); + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(failure); +} + +TEST(FailureReportsCorrectTestName) +{ + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + CHECK(false); + } + + CHECK_EQUAL(m_details.testName, reporter.lastFailedTest); +} + +TEST(CheckFailureIncludesCheckContents) +{ + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + const bool yaddayadda = false; + CHECK(yaddayadda); + } + + CHECK(strstr(reporter.lastFailedMessage, "yaddayadda")); +} + +TEST(CheckEqualSucceedsOnEqual) +{ + bool failure = true; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + CHECK_EQUAL(1, 1); + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(!failure); +} + +TEST(CheckEqualFailsOnNotEqual) +{ + bool failure = false; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + CHECK_EQUAL(1, 2); + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(failure); +} + +TEST(CheckEqualFailureContainsCorrectDetails) +{ + int line = 0; + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + UnitTest::TestDetails const testDetails("testName", "suiteName", "filename", -1); + ScopedCurrentTest scopedResults(testResults, &testDetails); + + CHECK_EQUAL(1, 123); line = __LINE__; + } + + CHECK_EQUAL("testName", reporter.lastFailedTest); + CHECK_EQUAL("suiteName", reporter.lastFailedSuite); + CHECK_EQUAL("filename", reporter.lastFailedFile); + CHECK_EQUAL(line, reporter.lastFailedLine); +} + +int g_sideEffect = 0; +int FunctionWithSideEffects() +{ + ++g_sideEffect; + return 1; +} + +TEST(CheckEqualDoesNotHaveSideEffectsWhenPassing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + CHECK_EQUAL(1, FunctionWithSideEffects()); + } + CHECK_EQUAL(1, g_sideEffect); +} + +TEST(CheckEqualDoesNotHaveSideEffectsWhenFailing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + CHECK_EQUAL(2, FunctionWithSideEffects()); + } + CHECK_EQUAL(1, g_sideEffect); +} + + +TEST(CheckCloseSucceedsOnEqual) +{ + bool failure = true; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + CHECK_CLOSE (1.0f, 1.001f, 0.01f); + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(!failure); +} + +TEST(CheckCloseFailsOnNotEqual) +{ + bool failure = false; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + CHECK_CLOSE (1.0f, 1.1f, 0.01f); + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(failure); +} + +TEST(CheckCloseFailureContainsCorrectDetails) +{ + int line = 0; + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + UnitTest::TestDetails testDetails("test", "suite", "filename", -1); + ScopedCurrentTest scopedResults(testResults, &testDetails); + + CHECK_CLOSE (1.0f, 1.1f, 0.01f); line = __LINE__; + } + + CHECK_EQUAL("test", reporter.lastFailedTest); + CHECK_EQUAL("suite", reporter.lastFailedSuite); + CHECK_EQUAL("filename", reporter.lastFailedFile); + CHECK_EQUAL(line, reporter.lastFailedLine); +} + +TEST(CheckCloseDoesNotHaveSideEffectsWhenPassing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + CHECK_CLOSE (1, FunctionWithSideEffects(), 0.1f); + } + CHECK_EQUAL(1, g_sideEffect); +} + +TEST(CheckCloseDoesNotHaveSideEffectsWhenFailing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + CHECK_CLOSE (2, FunctionWithSideEffects(), 0.1f); + } + CHECK_EQUAL(1, g_sideEffect); +} + +TEST(CheckArrayCloseSucceedsOnEqual) +{ + bool failure = true; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + const float data[4] = { 0, 1, 2, 3 }; + CHECK_ARRAY_CLOSE (data, data, 4, 0.01f); + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(!failure); +} + +TEST(CheckArrayCloseFailsOnNotEqual) +{ + bool failure = false; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + int const data1[4] = { 0, 1, 2, 3 }; + int const data2[4] = { 0, 1, 3, 3 }; + CHECK_ARRAY_CLOSE (data1, data2, 4, 0.01f); + + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(failure); +} + +TEST(CheckArrayCloseFailureIncludesCheckExpectedAndActual) +{ + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + int const data1[4] = { 0, 1, 2, 3 }; + int const data2[4] = { 0, 1, 3, 3 }; + CHECK_ARRAY_CLOSE (data1, data2, 4, 0.01f); + } + + CHECK(strstr(reporter.lastFailedMessage, "xpected [ 0 1 2 3 ]")); + CHECK(strstr(reporter.lastFailedMessage, "was [ 0 1 3 3 ]")); +} + +TEST(CheckArrayCloseFailureContainsCorrectDetails) +{ + int line = 0; + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + UnitTest::TestDetails testDetails("arrayCloseTest", "arrayCloseSuite", "filename", -1); + ScopedCurrentTest scopedResults(testResults, &testDetails); + + int const data1[4] = { 0, 1, 2, 3 }; + int const data2[4] = { 0, 1, 3, 3 }; + CHECK_ARRAY_CLOSE (data1, data2, 4, 0.01f); line = __LINE__; + } + + CHECK_EQUAL("arrayCloseTest", reporter.lastFailedTest); + CHECK_EQUAL("arrayCloseSuite", reporter.lastFailedSuite); + CHECK_EQUAL("filename", reporter.lastFailedFile); + CHECK_EQUAL(line, reporter.lastFailedLine); +} + +TEST(CheckArrayCloseFailureIncludesTolerance) +{ + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + float const data1[4] = { 0, 1, 2, 3 }; + float const data2[4] = { 0, 1, 3, 3 }; + CHECK_ARRAY_CLOSE (data1, data2, 4, 0.01f); + } + + CHECK(strstr(reporter.lastFailedMessage, "0.01")); +} + +TEST(CheckArrayEqualSuceedsOnEqual) +{ + bool failure = true; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + const float data[4] = { 0, 1, 2, 3 }; + CHECK_ARRAY_EQUAL (data, data, 4); + + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(!failure); +} + +TEST(CheckArrayEqualFailsOnNotEqual) +{ + bool failure = false; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + int const data1[4] = { 0, 1, 2, 3 }; + int const data2[4] = { 0, 1, 3, 3 }; + CHECK_ARRAY_EQUAL (data1, data2, 4); + + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(failure); +} + +TEST(CheckArrayEqualFailureIncludesCheckExpectedAndActual) +{ + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + int const data1[4] = { 0, 1, 2, 3 }; + int const data2[4] = { 0, 1, 3, 3 }; + CHECK_ARRAY_EQUAL (data1, data2, 4); + } + + CHECK(strstr(reporter.lastFailedMessage, "xpected [ 0 1 2 3 ]")); + CHECK(strstr(reporter.lastFailedMessage, "was [ 0 1 3 3 ]")); +} + +TEST(CheckArrayEqualFailureContainsCorrectInfo) +{ + int line = 0; + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + int const data1[4] = { 0, 1, 2, 3 }; + int const data2[4] = { 0, 1, 3, 3 }; + CHECK_ARRAY_EQUAL (data1, data2, 4); line = __LINE__; + } + + CHECK_EQUAL("CheckArrayEqualFailureContainsCorrectInfo", reporter.lastFailedTest); + CHECK_EQUAL(__FILE__, reporter.lastFailedFile); + CHECK_EQUAL(line, reporter.lastFailedLine); +} + +float const* FunctionWithSideEffects2() +{ + ++g_sideEffect; + static float const data[] = {1,2,3,4}; + return data; +} + +TEST(CheckArrayCloseDoesNotHaveSideEffectsWhenPassing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + + const float data[] = { 0, 1, 2, 3 }; + CHECK_ARRAY_CLOSE (data, FunctionWithSideEffects2(), 4, 0.01f); + } + CHECK_EQUAL(1, g_sideEffect); +} + +TEST(CheckArrayCloseDoesNotHaveSideEffectsWhenFailing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + + const float data[] = { 0, 1, 3, 3 }; + CHECK_ARRAY_CLOSE (data, FunctionWithSideEffects2(), 4, 0.01f); + } + + CHECK_EQUAL(1, g_sideEffect); +} + +TEST(CheckArray2DCloseSucceedsOnEqual) +{ + bool failure = true; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + const float data[2][2] = { {0, 1}, {2, 3} }; + CHECK_ARRAY2D_CLOSE (data, data, 2, 2, 0.01f); + + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(!failure); +} + +TEST(CheckArray2DCloseFailsOnNotEqual) +{ + bool failure = false; + { + RecordingReporter reporter; + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + int const data1[2][2] = { {0, 1}, {2, 3} }; + int const data2[2][2] = { {0, 1}, {3, 3} }; + CHECK_ARRAY2D_CLOSE (data1, data2, 2, 2, 0.01f); + + failure = (testResults.GetFailureCount() > 0); + } + + CHECK(failure); +} + +TEST(CheckArray2DCloseFailureIncludesCheckExpectedAndActual) +{ + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + int const data1[2][2] = { {0, 1}, {2, 3} }; + int const data2[2][2] = { {0, 1}, {3, 3} }; + + CHECK_ARRAY2D_CLOSE (data1, data2, 2, 2, 0.01f); + } + + CHECK(strstr(reporter.lastFailedMessage, "xpected [ [ 0 1 ] [ 2 3 ] ]")); + CHECK(strstr(reporter.lastFailedMessage, "was [ [ 0 1 ] [ 3 3 ] ]")); +} + +TEST(CheckArray2DCloseFailureContainsCorrectDetails) +{ + int line = 0; + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + UnitTest::TestDetails testDetails("array2DCloseTest", "array2DCloseSuite", "filename", -1); + ScopedCurrentTest scopedResults(testResults, &testDetails); + + int const data1[2][2] = { {0, 1}, {2, 3} }; + int const data2[2][2] = { {0, 1}, {3, 3} }; + CHECK_ARRAY2D_CLOSE (data1, data2, 2, 2, 0.01f); line = __LINE__; + } + + CHECK_EQUAL("array2DCloseTest", reporter.lastFailedTest); + CHECK_EQUAL("array2DCloseSuite", reporter.lastFailedSuite); + CHECK_EQUAL("filename", reporter.lastFailedFile); + CHECK_EQUAL(line, reporter.lastFailedLine); +} + +TEST(CheckArray2DCloseFailureIncludesTolerance) +{ + RecordingReporter reporter; + { + UnitTest::TestResults testResults(&reporter); + ScopedCurrentTest scopedResults(testResults); + + float const data1[2][2] = { {0, 1}, {2, 3} }; + float const data2[2][2] = { {0, 1}, {3, 3} }; + CHECK_ARRAY2D_CLOSE (data1, data2, 2, 2, 0.01f); + } + + CHECK(strstr(reporter.lastFailedMessage, "0.01")); +} + +float const* const* FunctionWithSideEffects3() +{ + ++g_sideEffect; + static float const data1[] = {0,1}; + static float const data2[] = {2,3}; + static const float* const data[] = {data1, data2}; + return data; +} + +TEST(CheckArray2DCloseDoesNotHaveSideEffectsWhenPassing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + + const float data[2][2] = { {0, 1}, {2, 3} }; + CHECK_ARRAY2D_CLOSE (data, FunctionWithSideEffects3(), 2, 2, 0.01f); + } + CHECK_EQUAL(1, g_sideEffect); +} + +TEST(CheckArray2DCloseDoesNotHaveSideEffectsWhenFailing) +{ + g_sideEffect = 0; + { + UnitTest::TestResults testResults; + ScopedCurrentTest scopedResults(testResults); + + const float data[2][2] = { {0, 1}, {3, 3} }; + CHECK_ARRAY2D_CLOSE (data, FunctionWithSideEffects3(), 2, 2, 0.01f); + } + CHECK_EQUAL(1, g_sideEffect); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestChecks.cpp b/tests/framework/UnitTestpp/src/tests/TestChecks.cpp new file mode 100644 index 0000000..face29e --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestChecks.cpp @@ -0,0 +1,318 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +using namespace UnitTest; + +namespace { + + +TEST(CheckEqualWithUnsignedLong) +{ + TestResults results; + unsigned long something = 2; + CHECK_EQUAL(something, something); +} + +TEST(CheckEqualsWithStringsFailsOnDifferentStrings) +{ + char txt1[] = "Hello"; + char txt2[] = "Hallo"; + TestResults results; + CheckEqual(results, "txt1", "txt2", txt1, txt2, TestDetails("", "", "", 0)); + CHECK_EQUAL(1, results.GetFailureCount()); +} + +char txt1[] = "Hello"; // non-const on purpose so no folding of duplicate data +char txt2[] = "Hello"; + +TEST(CheckEqualsWithStringsWorksOnContentsNonConstNonConst) +{ + char const* const p1 = txt1; + char const* const p2 = txt2; + TestResults results; + CheckEqual(results, "p1", "p2", p1, p2, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckEqualsWithStringsWorksOnContentsConstConst) +{ + char* const p1 = txt1; + char* const p2 = txt2; + TestResults results; + CheckEqual(results, "p1", "p2", p1, p2, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckEqualsWithStringsWorksOnContentsNonConstConst) +{ + char* const p1 = txt1; + char const* const p2 = txt2; + TestResults results; + CheckEqual(results, "p1", "p2", p1, p2, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckEqualsWithStringsWorksOnContentsConstNonConst) +{ + char const* const p1 = txt1; + char* const p2 = txt2; + TestResults results; + CheckEqual(results, "p1", "p2", p1, p2, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckEqualsWithStringsWorksOnContentsWithALiteral) +{ + char const* const p1 = txt1; + TestResults results; + CheckEqual(results, "Hello", "p1", "Hello", p1, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckEqualFailureIncludesCheckExpectedAndActual) +{ + RecordingReporter reporter; + TestResults results(&reporter); + const int something = 2; + CheckEqual(results, "1", "something", 1, something, TestDetails("", "", "", 0)); + + using namespace std; + CHECK(strstr(reporter.lastFailedMessage, "1=1")); + CHECK(strstr(reporter.lastFailedMessage, "something=2")); +} + +TEST(CheckEqualFailureIncludesDetails) +{ + RecordingReporter reporter; + TestResults results(&reporter); + TestDetails const details("mytest", "mysuite", "file.h", 101); + + CheckEqual(results, "1", "2", 1, 2, details); + + CHECK_EQUAL("mytest", reporter.lastFailedTest); + CHECK_EQUAL("mysuite", reporter.lastFailedSuite); + CHECK_EQUAL("file.h", reporter.lastFailedFile); + CHECK_EQUAL(101, reporter.lastFailedLine); +} + +TEST(CheckCloseTrue) +{ + TestResults results; + CheckClose(results, 3.001f, 3.0f, 0.1f, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckCloseFalse) +{ + TestResults results; + CheckClose(results, 3.12f, 3.0f, 0.1f, TestDetails("", "", "", 0)); + CHECK_EQUAL(1, results.GetFailureCount()); +} + +TEST(CheckCloseWithZeroEpsilonWorksForSameNumber) +{ + TestResults results; + CheckClose(results, 0.1f, 0.1f, 0, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckCloseWithNaNFails) +{ + const unsigned int bitpattern = 0xFFFFFFFF; + float nan; + std::memcpy(&nan, &bitpattern, sizeof(bitpattern)); + + TestResults results; + CheckClose(results, 3.0f, nan, 0.1f, TestDetails("", "", "", 0)); + CHECK_EQUAL(1, results.GetFailureCount()); +} + +TEST(CheckCloseWithNaNAgainstItselfFails) +{ + const unsigned int bitpattern = 0xFFFFFFFF; + float nan; + std::memcpy(&nan, &bitpattern, sizeof(bitpattern)); + + TestResults results; + CheckClose(results, nan, nan, 0.1f, TestDetails("", "", "", 0)); + CHECK_EQUAL(1, results.GetFailureCount()); +} + +TEST(CheckCloseFailureIncludesCheckExpectedAndActual) +{ + RecordingReporter reporter; + TestResults results(&reporter); + const float expected = 0.9f; + const float actual = 1.1f; + CheckClose(results, expected, actual, 0.01f, TestDetails("", "", "", 0)); + + using namespace std; + CHECK(strstr(reporter.lastFailedMessage, "xpected 0.9")); + CHECK(strstr(reporter.lastFailedMessage, "was 1.1")); +} + +TEST(CheckCloseFailureIncludesTolerance) +{ + RecordingReporter reporter; + TestResults results(&reporter); + CheckClose(results, 2, 3, 0.01f, TestDetails("", "", "", 0)); + + using namespace std; + CHECK(strstr(reporter.lastFailedMessage, "0.01")); +} + +TEST(CheckCloseFailureIncludesDetails) +{ + RecordingReporter reporter; + TestResults results(&reporter); + TestDetails const details("mytest", "mysuite", "header.h", 10); + + CheckClose(results, 2, 3, 0.01f, details); + + CHECK_EQUAL("mytest", reporter.lastFailedTest); + CHECK_EQUAL("mysuite", reporter.lastFailedSuite); + CHECK_EQUAL("header.h", reporter.lastFailedFile); + CHECK_EQUAL(10, reporter.lastFailedLine); +} + + +TEST(CheckArrayEqualTrue) +{ + TestResults results; + + int const array[3] = { 1, 2, 3 }; + CheckArrayEqual(results, array, array, 3, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckArrayEqualFalse) +{ + TestResults results; + + int const array1[3] = { 1, 2, 3 }; + int const array2[3] = { 1, 2, 2 }; + CheckArrayEqual(results, array1, array2, 3, TestDetails("", "", "", 0)); + CHECK_EQUAL(1, results.GetFailureCount()); +} + +TEST(CheckArrayCloseTrue) +{ + TestResults results; + + float const array1[3] = { 1.0f, 1.5f, 2.0f }; + float const array2[3] = { 1.01f, 1.51f, 2.01f }; + CheckArrayClose(results, array1, array2, 3, 0.02f, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckArrayCloseFalse) +{ + TestResults results; + + float const array1[3] = { 1.0f, 1.5f, 2.0f }; + float const array2[3] = { 1.01f, 1.51f, 2.01f }; + CheckArrayClose(results, array1, array2, 3, 0.001f, TestDetails("", "", "", 0)); + CHECK_EQUAL(1, results.GetFailureCount()); +} + +TEST(CheckArrayCloseFailureIncludesDetails) +{ + RecordingReporter reporter; + TestResults results(&reporter); + TestDetails const details("arrayCloseTest", "arrayCloseSuite", "file", 1337); + + float const array1[3] = { 1.0f, 1.5f, 2.0f }; + float const array2[3] = { 1.01f, 1.51f, 2.01f }; + CheckArrayClose(results, array1, array2, 3, 0.001f, details); + + CHECK_EQUAL("arrayCloseTest", reporter.lastFailedTest); + CHECK_EQUAL("arrayCloseSuite", reporter.lastFailedSuite); + CHECK_EQUAL("file", reporter.lastFailedFile); + CHECK_EQUAL(1337, reporter.lastFailedLine); +} + + +TEST(CheckArray2DCloseTrue) +{ + TestResults results; + + float const array1[3][3] = { { 1.0f, 1.5f, 2.0f }, + { 2.0f, 2.5f, 3.0f }, + { 3.0f, 3.5f, 4.0f } }; + float const array2[3][3] = { { 1.01f, 1.51f, 2.01f }, + { 2.01f, 2.51f, 3.01f }, + { 3.01f, 3.51f, 4.01f } }; + CheckArray2DClose(results, array1, array2, 3, 3, 0.02f, TestDetails("", "", "", 0)); + CHECK_EQUAL(0, results.GetFailureCount()); +} + +TEST(CheckArray2DCloseFalse) +{ + TestResults results; + + float const array1[3][3] = { { 1.0f, 1.5f, 2.0f }, + { 2.0f, 2.5f, 3.0f }, + { 3.0f, 3.5f, 4.0f } }; + float const array2[3][3] = { { 1.01f, 1.51f, 2.01f }, + { 2.01f, 2.51f, 3.01f }, + { 3.01f, 3.51f, 4.01f } }; + CheckArray2DClose(results, array1, array2, 3, 3, 0.001f, TestDetails("", "", "", 0)); + CHECK_EQUAL(1, results.GetFailureCount()); +} + +TEST(CheckCloseWithDoublesSucceeds) +{ + CHECK_CLOSE(0.5, 0.5, 0.0001); +} + +TEST(CheckArray2DCloseFailureIncludesDetails) +{ + RecordingReporter reporter; + TestResults results(&reporter); + TestDetails const details("array2DCloseTest", "array2DCloseSuite", "file", 1234); + + float const array1[3][3] = { { 1.0f, 1.5f, 2.0f }, + { 2.0f, 2.5f, 3.0f }, + { 3.0f, 3.5f, 4.0f } }; + float const array2[3][3] = { { 1.01f, 1.51f, 2.01f }, + { 2.01f, 2.51f, 3.01f }, + { 3.01f, 3.51f, 4.01f } }; + CheckArray2DClose(results, array1, array2, 3, 3, 0.001f, details); + + CHECK_EQUAL("array2DCloseTest", reporter.lastFailedTest); + CHECK_EQUAL("array2DCloseSuite", reporter.lastFailedSuite); + CHECK_EQUAL("file", reporter.lastFailedFile); + CHECK_EQUAL(1234, reporter.lastFailedLine); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestCompositeTestReporter.cpp b/tests/framework/UnitTestpp/src/tests/TestCompositeTestReporter.cpp new file mode 100644 index 0000000..0a2cf00 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestCompositeTestReporter.cpp @@ -0,0 +1,208 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +#include "../CompositeTestReporter.h" + +using namespace UnitTest; + +namespace { + +TEST(ZeroReportersByDefault) +{ + CHECK_EQUAL(0, CompositeTestReporter().GetReporterCount()); +} + +struct MockReporter : TestReporter +{ + MockReporter() + : testStartCalled(false) + , testStartDetails(NULL) + , failureCalled(false) + , failureDetails(NULL) + , failureStr(NULL) + , testFinishCalled(false) + , testFinishDetails(NULL) + , testFinishSecondsElapsed(-1.0f) + , summaryCalled(false) + , summaryTotalTestCount(-1) + , summaryFailureCount(-1) + , summarySecondsElapsed(-1.0f) + { + } + + virtual void ReportTestStart(TestDetails const& test) + { + testStartCalled = true; + testStartDetails = &test; + } + + virtual void ReportFailure(TestDetails const& test, char const* failure) + { + failureCalled = true; + failureDetails = &test; + failureStr = failure; + } + + virtual void ReportTestFinish(TestDetails const& test, bool, float secondsElapsed) + { + testFinishCalled = true; + testFinishDetails = &test; + testFinishSecondsElapsed = secondsElapsed; + } + + virtual void ReportSummary(int totalTestCount, + int failedTestCount, + int failureCount, + float secondsElapsed) + { + summaryCalled = true; + summaryTotalTestCount = totalTestCount; + summaryFailedTestCount = failedTestCount; + summaryFailureCount = failureCount; + summarySecondsElapsed = secondsElapsed; + } + + bool testStartCalled; + TestDetails const* testStartDetails; + + bool failureCalled; + TestDetails const* failureDetails; + const char* failureStr; + + bool testFinishCalled; + TestDetails const* testFinishDetails; + float testFinishSecondsElapsed; + + bool summaryCalled; + int summaryTotalTestCount; + int summaryFailedTestCount; + int summaryFailureCount; + float summarySecondsElapsed; +}; + +TEST(AddReporter) +{ + MockReporter r; + CompositeTestReporter c; + + CHECK(c.AddReporter(&r)); + CHECK_EQUAL(1, c.GetReporterCount()); +} + +TEST(RemoveReporter) +{ + MockReporter r; + CompositeTestReporter c; + + c.AddReporter(&r); + CHECK(c.RemoveReporter(&r)); + CHECK_EQUAL(0, c.GetReporterCount()); +} + +struct Fixture +{ + Fixture() + { + c.AddReporter(&r0); + c.AddReporter(&r1); + } + + MockReporter r0, r1; + CompositeTestReporter c; +}; + +TEST_FIXTURE(Fixture, ReportTestStartCallsReportTestStartOnAllAggregates) +{ + TestDetails t("", "", "", 0); + c.ReportTestStart(t); + + CHECK(r0.testStartCalled); + CHECK_EQUAL(&t, r0.testStartDetails); + CHECK(r1.testStartCalled); + CHECK_EQUAL(&t, r1.testStartDetails); +} + +TEST_FIXTURE(Fixture, ReportFailureCallsReportFailureOnAllAggregates) +{ + TestDetails t("", "", "", 0); + const char* failStr = "fail"; + c.ReportFailure(t, failStr); + + CHECK(r0.failureCalled); + CHECK_EQUAL(&t, r0.failureDetails); + CHECK_EQUAL(failStr, r0.failureStr); + + CHECK(r1.failureCalled); + CHECK_EQUAL(&t, r1.failureDetails); + CHECK_EQUAL(failStr, r1.failureStr); +} + +TEST_FIXTURE(Fixture, ReportTestFinishCallsReportTestFinishOnAllAggregates) +{ + TestDetails t("", "", "", 0); + const float s = 1.2345f; + c.ReportTestFinish(t, true, s); + + CHECK(r0.testFinishCalled); + CHECK_EQUAL(&t, r0.testFinishDetails); + CHECK_CLOSE(s, r0.testFinishSecondsElapsed, 0.00001f); + + CHECK(r1.testFinishCalled); + CHECK_EQUAL(&t, r1.testFinishDetails); + CHECK_CLOSE(s, r1.testFinishSecondsElapsed, 0.00001f); +} + +TEST_FIXTURE(Fixture, ReportSummaryCallsReportSummaryOnAllAggregates) +{ + TestDetails t("", "", "", 0); + const int testCount = 3; + const int failedTestCount = 4; + const int failureCount = 5; + const float secondsElapsed = 3.14159f; + + c.ReportSummary(testCount, failedTestCount, failureCount, secondsElapsed); + + CHECK(r0.summaryCalled); + CHECK_EQUAL(testCount, r0.summaryTotalTestCount); + CHECK_EQUAL(failedTestCount, r0.summaryFailedTestCount); + CHECK_EQUAL(failureCount, r0.summaryFailureCount); + CHECK_CLOSE(secondsElapsed, r0.summarySecondsElapsed, 0.00001f); + + CHECK(r1.summaryCalled); + CHECK_EQUAL(testCount, r1.summaryTotalTestCount); + CHECK_EQUAL(failedTestCount, r1.summaryFailedTestCount); + CHECK_EQUAL(failureCount, r1.summaryFailureCount); + CHECK_CLOSE(secondsElapsed, r1.summarySecondsElapsed, 0.00001f); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestCurrentTest.cpp b/tests/framework/UnitTestpp/src/tests/TestCurrentTest.cpp new file mode 100644 index 0000000..daa6d4a --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestCurrentTest.cpp @@ -0,0 +1,67 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +namespace +{ + +TEST(CanSetandGetDetails) +{ + bool ok = false; + { + ScopedCurrentTest scopedTest; + + const UnitTest::TestDetails* details = reinterpret_cast< const UnitTest::TestDetails* >(12345); + UnitTest::CurrentTest::Details() = details; + + ok = (UnitTest::CurrentTest::Details() == details); + } + + CHECK(ok); +} + +TEST(CanSetAndGetResults) +{ + bool ok = false; + { + ScopedCurrentTest scopedTest; + + UnitTest::TestResults results; + UnitTest::CurrentTest::Results() = &results; + + ok = (UnitTest::CurrentTest::Results() == &results); + } + + CHECK(ok); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestDeferredTestReporter.cpp b/tests/framework/UnitTestpp/src/tests/TestDeferredTestReporter.cpp new file mode 100644 index 0000000..ac19b67 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestDeferredTestReporter.cpp @@ -0,0 +1,151 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +#include "../DeferredTestReporter.h" + +namespace UnitTest +{ + +namespace +{ + +#ifndef UNITTEST_MEMORYOUTSTREAM_IS_STD_OSTRINGSTREAM + MemoryOutStream& operator <<(MemoryOutStream& lhs, const std::string& rhs) + { + lhs << rhs.c_str(); + return lhs; + } +#endif + +struct MockDeferredTestReporter : public DeferredTestReporter +{ + virtual void ReportSummary(int, int, int, float) + { + } +}; + +struct DeferredTestReporterFixture +{ + DeferredTestReporterFixture() + : testName("UniqueTestName") + , testSuite("UniqueTestSuite") + , fileName("filename.h") + , lineNumber(12) + , details(testName.c_str(), testSuite.c_str(), fileName.c_str(), lineNumber) + { + } + + MockDeferredTestReporter reporter; + std::string const testName; + std::string const testSuite; + std::string const fileName; + int const lineNumber; + TestDetails const details; +}; + +TEST_FIXTURE(DeferredTestReporterFixture, ReportTestStartCreatesANewDeferredTest) +{ + reporter.ReportTestStart(details); + CHECK_EQUAL(1, (int)reporter.GetResults().size()); +} + +TEST_FIXTURE(DeferredTestReporterFixture, ReportTestStartCapturesTestNameAndSuite) +{ + reporter.ReportTestStart(details); + + DeferredTestResult const& result = reporter.GetResults().at(0); + CHECK_EQUAL(testName.c_str(), result.testName); + CHECK_EQUAL(testSuite.c_str(), result.suiteName); +} + +TEST_FIXTURE(DeferredTestReporterFixture, ReportTestEndCapturesTestTime) +{ + float const elapsed = 123.45f; + reporter.ReportTestStart(details); + reporter.ReportTestFinish(details, true, elapsed); + + DeferredTestResult const& result = reporter.GetResults().at(0); + CHECK_CLOSE(elapsed, result.timeElapsed, 0.0001f); +} + +TEST_FIXTURE(DeferredTestReporterFixture, ReportFailureSavesFailureDetails) +{ + char const* failure = "failure"; + + reporter.ReportTestStart(details); + reporter.ReportFailure(details, failure); + + DeferredTestResult const& result = reporter.GetResults().at(0); + CHECK(result.failed == true); + CHECK_EQUAL(fileName.c_str(), result.failureFile); +} + +TEST_FIXTURE(DeferredTestReporterFixture, ReportFailureSavesFailureDetailsForMultipleFailures) +{ + char const* failure1 = "failure 1"; + char const* failure2 = "failure 2"; + + reporter.ReportTestStart(details); + reporter.ReportFailure(details, failure1); + reporter.ReportFailure(details, failure2); + + DeferredTestResult const& result = reporter.GetResults().at(0); + CHECK_EQUAL(2, (int)result.failures.size()); + CHECK_EQUAL(failure1, result.failures[0].failureStr); + CHECK_EQUAL(failure2, result.failures[1].failureStr); +} + +TEST_FIXTURE(DeferredTestReporterFixture, DeferredTestReporterTakesCopyOfFailureMessage) +{ + reporter.ReportTestStart(details); + + char failureMessage[128]; + char const* goodStr = "Real failure message"; + char const* badStr = "Bogus failure message"; + + using namespace std; + + strcpy(failureMessage, goodStr); + reporter.ReportFailure(details, failureMessage); + strcpy(failureMessage, badStr); + + DeferredTestResult const& result = reporter.GetResults().at(0); + DeferredTestFailure const& failure = result.failures.at(0); + CHECK_EQUAL(goodStr, failure.failureStr); +} + +}} + +#endif diff --git a/tests/framework/UnitTestpp/src/tests/TestMemoryOutStream.cpp b/tests/framework/UnitTestpp/src/tests/TestMemoryOutStream.cpp new file mode 100644 index 0000000..c4345bc --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestMemoryOutStream.cpp @@ -0,0 +1,205 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +#include "../MemoryOutStream.h" +#include +#include + +using namespace UnitTest; +using namespace std; + +namespace { + +TEST(DefaultIsEmptyString) +{ + MemoryOutStream const stream; + CHECK(stream.GetText() != 0); + CHECK_EQUAL("", stream.GetText()); +} + +TEST(StreamingTextCopiesCharacters) +{ + MemoryOutStream stream; + stream << "Lalala"; + CHECK_EQUAL("Lalala", stream.GetText()); +} + +TEST(StreamingMultipleTimesConcatenatesResult) +{ + MemoryOutStream stream; + stream << "Bork" << "To" << "Fred"; + CHECK_EQUAL("BorkToFred", stream.GetText()); +} + +TEST(StreamingIntWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << (int)123; + CHECK_EQUAL("123", stream.GetText()); +} + +TEST(StreamingUnsignedIntWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << (unsigned int)123; + CHECK_EQUAL("123", stream.GetText()); +} + +TEST(StreamingLongWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << (long)(-123); + CHECK_EQUAL("-123", stream.GetText()); +} + +TEST(StreamingUnsignedLongWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << (unsigned long)123; + CHECK_EQUAL("123", stream.GetText()); +} + +TEST(StreamingLongLongWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << (long long)(ULONG_MAX) * 2; + CHECK_EQUAL("8589934590", stream.GetText()); +} + +TEST(StreamingUnsignedLongLongWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << (unsigned long long)(ULONG_MAX) * 2; + CHECK_EQUAL("8589934590", stream.GetText()); +} + +TEST(StreamingFloatWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << 3.1415f; + CHECK(strstr(stream.GetText(), "3.1415")); +} + +TEST(StreamingDoubleWritesCorrectCharacters) +{ + MemoryOutStream stream; + stream << 3.1415; + CHECK(strstr(stream.GetText(), "3.1415")); +} + +TEST(StreamingPointerWritesCorrectCharacters) +{ + MemoryOutStream stream; + int* p = (int*)0x1234; + stream << p; + CHECK(strstr(stream.GetText(), "1234")); +} + +TEST(StreamingSizeTWritesCorrectCharacters) +{ + MemoryOutStream stream; + size_t const s = 53124; + stream << s; + CHECK_EQUAL("53124", stream.GetText()); +} + +TEST(ClearEmptiesMemoryOutStreamContents) +{ + MemoryOutStream stream; + stream << "Hello world"; + stream.Clear(); + CHECK_EQUAL("", stream.GetText()); +} + +#ifndef UNITTEST_MEMORYOUTSTREAM_IS_STD_OSTRINGSTREAM + +TEST(StreamInitialCapacityIsCorrect) +{ + MemoryOutStream stream(MemoryOutStream::GROW_CHUNK_SIZE); + CHECK_EQUAL((int)MemoryOutStream::GROW_CHUNK_SIZE, stream.GetCapacity()); +} + +TEST(StreamInitialCapacityIsMultipleOfGrowChunkSize) +{ + MemoryOutStream stream(MemoryOutStream::GROW_CHUNK_SIZE + 1); + CHECK_EQUAL((int)MemoryOutStream::GROW_CHUNK_SIZE * 2, stream.GetCapacity()); +} + + +TEST(ExceedingCapacityGrowsBuffer) +{ + MemoryOutStream stream(MemoryOutStream::GROW_CHUNK_SIZE); + stream << "012345678901234567890123456789"; + char const* const oldBuffer = stream.GetText(); + stream << "0123456789"; + CHECK(oldBuffer != stream.GetText()); +} + +TEST(ExceedingCapacityGrowsBufferByGrowChunk) +{ + MemoryOutStream stream(MemoryOutStream::GROW_CHUNK_SIZE); + stream << "0123456789012345678901234567890123456789"; + CHECK_EQUAL(MemoryOutStream::GROW_CHUNK_SIZE * 2, stream.GetCapacity()); +} + +TEST(WritingStringLongerThanCapacityFitsInNewBuffer) +{ + MemoryOutStream stream(8); + stream << "0123456789ABCDEF"; + CHECK_EQUAL("0123456789ABCDEF", stream.GetText()); +} + +TEST(WritingIntLongerThanCapacityFitsInNewBuffer) +{ + MemoryOutStream stream(8); + stream << "aaaa" << 123456;; + CHECK_EQUAL("aaaa123456", stream.GetText()); +} + +TEST(WritingFloatLongerThanCapacityFitsInNewBuffer) +{ + MemoryOutStream stream(8); + stream << "aaaa" << 123456.0f;; + CHECK_EQUAL("aaaa123456.000000f", stream.GetText()); +} + +TEST(WritingSizeTLongerThanCapacityFitsInNewBuffer) +{ + MemoryOutStream stream(8); + stream << "aaaa" << size_t(32145); + CHECK_EQUAL("aaaa32145", stream.GetText()); +} + +#endif + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestTest.cpp b/tests/framework/UnitTestpp/src/tests/TestTest.cpp new file mode 100644 index 0000000..c7377b7 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestTest.cpp @@ -0,0 +1,158 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +using namespace UnitTest; + +namespace { + +TEST(PassingTestHasNoFailures) +{ + class PassingTest : public Test + { + public: + PassingTest() : Test("passing") {} + virtual void RunImpl() const + { + CHECK(true); + } + }; + + TestResults results; + { + ScopedCurrentTest scopedResults(results); + PassingTest().Run(); + } + + CHECK_EQUAL(0, results.GetFailureCount()); +} + + +TEST(FailingTestHasFailures) +{ + class FailingTest : public Test + { + public: + FailingTest() : Test("failing") {} + virtual void RunImpl() const + { + CHECK(false); + } + }; + + TestResults results; + { + ScopedCurrentTest scopedResults(results); + FailingTest().Run(); + } + + CHECK_EQUAL(1, results.GetFailureCount()); +} + +#ifndef UNITTEST_NO_EXCEPTIONS +TEST(ThrowingTestsAreReportedAsFailures) +{ + class CrashingTest : public Test + { + public: + CrashingTest() : Test("throwing") {} + virtual void RunImpl() const + { + throw "Blah"; + } + }; + + TestResults results; + { + ScopedCurrentTest scopedResult(results); + CrashingTest().Run(); + } + + CHECK_EQUAL(1, results.GetFailureCount()); +} +/* +#ifndef UNITTEST_MINGW +TEST(CrashingTestsAreReportedAsFailures) +{ + class CrashingTest : public Test + { + public: + CrashingTest() : Test("crashing") {} + virtual void RunImpl() const + { + reinterpret_cast< void (*)() >(0)(); + } + }; + + TestResults results; + { + ScopedCurrentTest scopedResult(results); + CrashingTest().Run(); + } + + CHECK_EQUAL(1, results.GetFailureCount()); +} +#endif +*/ +#endif + +TEST(TestWithUnspecifiedSuiteGetsDefaultSuite) +{ + Test test("test"); + CHECK(test.m_details.suiteName != NULL); + CHECK_EQUAL("DefaultSuite", test.m_details.suiteName); +} + +TEST(TestReflectsSpecifiedSuiteName) +{ + Test test("test", "testSuite"); + CHECK(test.m_details.suiteName != NULL); + CHECK_EQUAL("testSuite", test.m_details.suiteName); +} + +void Fail() +{ + CHECK(false); +} + +TEST(OutOfCoreCHECKMacrosCanFailTests) +{ + TestResults results; + { + ScopedCurrentTest scopedResult(results); + Fail(); + } + + CHECK_EQUAL(1, results.GetFailureCount()); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestTestList.cpp b/tests/framework/UnitTestpp/src/tests/TestTestList.cpp new file mode 100644 index 0000000..ddb6938 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestTestList.cpp @@ -0,0 +1,80 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +using namespace UnitTest; + +namespace { + + +TEST(TestListIsEmptyByDefault) +{ + TestList list; + CHECK(list.GetFirst() == 0); +} + +TEST(AddingTestSetsHeadToTest) +{ + Test test("test"); + TestList list; + list.Add(&test); + + CHECK(list.GetFirst() == &test); + CHECK(test.m_nextTest == 0); +} + +TEST(AddingSecondTestAddsItToEndOfList) +{ + Test test1("test1"); + Test test2("test2"); + + TestList list; + list.Add(&test1); + list.Add(&test2); + + CHECK(list.GetFirst() == &test1); + CHECK(test1.m_nextTest == &test2); + CHECK(test2.m_nextTest == 0); +} + +TEST(ListAdderAddsTestToList) +{ + TestList list; + + Test test(""); + ListAdder adder(list, &test, nullptr); + + CHECK(list.GetFirst() == &test); + CHECK(test.m_nextTest == 0); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestTestMacros.cpp b/tests/framework/UnitTestpp/src/tests/TestTestMacros.cpp new file mode 100644 index 0000000..f469e20 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestTestMacros.cpp @@ -0,0 +1,253 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +using namespace UnitTest; +using namespace std; + +#ifdef __APPLE__ +extern "C" UnitTest::TestList& UnitTest::GetTestList() +{ + static TestList s_list; + return s_list; +} +#endif + +namespace { + +TestList list1; +TEST_EX(DummyTest, list1) +{ +} + +TEST (TestsAreAddedToTheListThroughMacro) +{ + CHECK(list1.GetFirst() != 0); + CHECK(list1.GetFirst()->m_nextTest == 0); +} + +#ifndef UNITTEST_NO_EXCEPTIONS + +struct ThrowingThingie +{ + ThrowingThingie() : dummy(false) + { + if (!dummy) + throw "Oops"; + } + + bool dummy; +}; + +TestList list2; +TEST_FIXTURE_EX(ThrowingThingie, DummyTestName, list2) +{ +} + +TEST (ExceptionsInFixtureAreReportedAsHappeningInTheFixture) +{ + RecordingReporter reporter; + TestResults result(&reporter); + { + ScopedCurrentTest scopedResults(result); + list2.GetFirst()->Run(); + } + + CHECK(strstr(reporter.lastFailedMessage, "xception")); + CHECK(strstr(reporter.lastFailedMessage, "fixture")); + CHECK(strstr(reporter.lastFailedMessage, "ThrowingThingie")); +} + +#endif + +struct DummyFixture +{ + int x; +}; + +// We're really testing the macros so we just want them to compile and link +SUITE(TestSuite1) +{ + TEST(SimilarlyNamedTestsInDifferentSuitesWork) + { + } + + TEST_FIXTURE(DummyFixture, SimilarlyNamedFixtureTestsInDifferentSuitesWork) + { + } +} + +SUITE(TestSuite2) +{ + TEST(SimilarlyNamedTestsInDifferentSuitesWork) + { + } + + TEST_FIXTURE(DummyFixture,SimilarlyNamedFixtureTestsInDifferentSuitesWork) + { + } +} + +TestList macroTestList1; +TEST_EX(MacroTestHelper1, macroTestList1) +{ +} + +TEST(TestAddedWithTEST_EXMacroGetsDefaultSuite) +{ + CHECK(macroTestList1.GetFirst() != NULL); + CHECK_EQUAL ("MacroTestHelper1", macroTestList1.GetFirst()->m_details.testName); + CHECK_EQUAL ("DefaultSuite", macroTestList1.GetFirst()->m_details.suiteName); +} + +TestList macroTestList2; +TEST_FIXTURE_EX(DummyFixture, MacroTestHelper2, macroTestList2) +{ +} + +TEST(TestAddedWithTEST_FIXTURE_EXMacroGetsDefaultSuite) +{ + CHECK(macroTestList2.GetFirst() != NULL); + CHECK_EQUAL ("MacroTestHelper2", macroTestList2.GetFirst()->m_details.testName); + CHECK_EQUAL ("DefaultSuite", macroTestList2.GetFirst()->m_details.suiteName); +} + +#ifndef UNITTEST_NO_EXCEPTIONS + +struct FixtureCtorThrows +{ + FixtureCtorThrows() { throw "exception"; } +}; + +TestList throwingFixtureTestList1; +TEST_FIXTURE_EX(FixtureCtorThrows, FixtureCtorThrowsTestName, throwingFixtureTestList1) +{ +} + +TEST(FixturesWithThrowingCtorsAreFailures) +{ + CHECK(throwingFixtureTestList1.GetFirst() != NULL); + RecordingReporter reporter; + TestResults result(&reporter); + { + ScopedCurrentTest scopedResult(result); + throwingFixtureTestList1.GetFirst()->Run(); + } + + int const failureCount = result.GetFailedTestCount(); + CHECK_EQUAL(1, failureCount); + CHECK(strstr(reporter.lastFailedMessage, "while constructing fixture")); +} + +struct FixtureDtorThrows +{ + ~FixtureDtorThrows() { throw "exception"; } +}; + +TestList throwingFixtureTestList2; +TEST_FIXTURE_EX(FixtureDtorThrows, FixtureDtorThrowsTestName, throwingFixtureTestList2) +{ +} + +TEST(FixturesWithThrowingDtorsAreFailures) +{ + CHECK(throwingFixtureTestList2.GetFirst() != NULL); + + RecordingReporter reporter; + TestResults result(&reporter); + { + ScopedCurrentTest scopedResult(result); + throwingFixtureTestList2.GetFirst()->Run(); + } + + int const failureCount = result.GetFailedTestCount(); + CHECK_EQUAL(1, failureCount); + CHECK(strstr(reporter.lastFailedMessage, "while destroying fixture")); +} + +const int FailingLine = 123; + +struct FixtureCtorAsserts +{ + FixtureCtorAsserts() + { + UnitTest::ReportAssert("assert failure", "file", FailingLine); + } +}; + +TestList ctorAssertFixtureTestList; +TEST_FIXTURE_EX(FixtureCtorAsserts, CorrectlyReportsAssertFailureInCtor, ctorAssertFixtureTestList) +{ +} + +TEST(CorrectlyReportsFixturesWithCtorsThatAssert) +{ + RecordingReporter reporter; + TestResults result(&reporter); + { + ScopedCurrentTest scopedResults(result); + ctorAssertFixtureTestList.GetFirst()->Run(); + } + + const int failureCount = result.GetFailedTestCount(); + CHECK_EQUAL(1, failureCount); + CHECK_EQUAL(FailingLine, reporter.lastFailedLine); + CHECK(strstr(reporter.lastFailedMessage, "assert failure")); +} + +#endif + +} + +// We're really testing if it's possible to use the same suite in two files +// to compile and link successfuly (TestTestSuite.cpp has suite with the same name) +// Note: we are outside of the anonymous namespace +SUITE(SameTestSuite) +{ + TEST(DummyTest1) + { + } +} + +#define CUR_TEST_NAME CurrentTestDetailsContainCurrentTestInfo +#define INNER_STRINGIFY(X) #X +#define STRINGIFY(X) INNER_STRINGIFY(X) + +TEST(CUR_TEST_NAME) +{ + const UnitTest::TestDetails* details = CurrentTest::Details(); + CHECK_EQUAL(STRINGIFY(CUR_TEST_NAME), details->testName); +} + +#undef CUR_TEST_NAME +#undef INNER_STRINGIFY +#undef STRINGIFY diff --git a/tests/framework/UnitTestpp/src/tests/TestTestResults.cpp b/tests/framework/UnitTestpp/src/tests/TestTestResults.cpp new file mode 100644 index 0000000..b63b87d --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestTestResults.cpp @@ -0,0 +1,140 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +using namespace UnitTest; + +namespace { + +TestDetails const g_testdetails("testname", "suitename", "filename", 123); + + +TEST(StartsWithNoTestsRun) +{ + TestResults results; + CHECK_EQUAL (0, results.GetTotalTestCount()); +} + +TEST(RecordsNumbersOfTests) +{ + TestResults results; + results.OnTestStart(g_testdetails); + results.OnTestStart(g_testdetails); + results.OnTestStart(g_testdetails); + CHECK_EQUAL(3, results.GetTotalTestCount()); +} + +TEST(StartsWithNoTestsFailing) +{ + TestResults results; + CHECK_EQUAL (0, results.GetFailureCount()); +} + +TEST(RecordsNumberOfFailures) +{ + TestResults results; + results.OnTestFailure(g_testdetails, ""); + results.OnTestFailure(g_testdetails, ""); + CHECK_EQUAL(2, results.GetFailureCount()); +} + +TEST(RecordsNumberOfFailedTests) +{ + TestResults results; + + results.OnTestStart(g_testdetails); + results.OnTestFailure(g_testdetails, ""); + results.OnTestFinish(g_testdetails, 0); + + results.OnTestStart(g_testdetails); + results.OnTestFailure(g_testdetails, ""); + results.OnTestFailure(g_testdetails, ""); + results.OnTestFailure(g_testdetails, ""); + results.OnTestFinish(g_testdetails, 0); + + CHECK_EQUAL (2, results.GetFailedTestCount()); +} + +TEST(NotifiesReporterOfTestStartWithCorrectInfo) +{ + RecordingReporter reporter; + TestResults results(&reporter); + results.OnTestStart(g_testdetails); + + CHECK_EQUAL (1, reporter.testRunCount); + CHECK_EQUAL ("suitename", reporter.lastStartedSuite); + CHECK_EQUAL ("testname", reporter.lastStartedTest); +} + +TEST(NotifiesReporterOfTestFailureWithCorrectInfo) +{ + RecordingReporter reporter; + TestResults results(&reporter); + + results.OnTestFailure(g_testdetails, "failurestring"); + CHECK_EQUAL (1, reporter.testFailedCount); + CHECK_EQUAL ("filename", reporter.lastFailedFile); + CHECK_EQUAL (123, reporter.lastFailedLine); + CHECK_EQUAL ("suitename", reporter.lastFailedSuite); + CHECK_EQUAL ("testname", reporter.lastFailedTest); + CHECK_EQUAL ("failurestring", reporter.lastFailedMessage); +} + +TEST(NotifiesReporterOfCheckFailureWithCorrectInfo) +{ + RecordingReporter reporter; + TestResults results(&reporter); + + results.OnTestFailure(g_testdetails, "failurestring"); + CHECK_EQUAL (1, reporter.testFailedCount); + + CHECK_EQUAL ("filename", reporter.lastFailedFile); + CHECK_EQUAL (123, reporter.lastFailedLine); + CHECK_EQUAL ("testname", reporter.lastFailedTest); + CHECK_EQUAL ("suitename", reporter.lastFailedSuite); + CHECK_EQUAL ("failurestring", reporter.lastFailedMessage); +} + +TEST(NotifiesReporterOfTestEnd) +{ + RecordingReporter reporter; + TestResults results(&reporter); + + results.OnTestFinish(g_testdetails, 0.1234f); + CHECK_EQUAL (1, reporter.testFinishedCount); + CHECK_EQUAL ("testname", reporter.lastFinishedTest); + CHECK_EQUAL ("suitename", reporter.lastFinishedSuite); + CHECK_CLOSE (0.1234f, reporter.lastFinishedTestTime, 0.0001f); +} + + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestTestRunner.cpp b/tests/framework/UnitTestpp/src/tests/TestTestRunner.cpp new file mode 100644 index 0000000..f5199a2 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestTestRunner.cpp @@ -0,0 +1,303 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +#include "../Test.h" + +using namespace UnitTest; + +namespace +{ + +struct TestRunnerFixture +{ + TestRunnerFixture() + : runner(reporter) + { + s_testRunnerFixtureTestResults = runner.GetTestResults(); + } + + static TestResults* s_testRunnerFixtureTestResults; + + RecordingReporter reporter; + TestList list; + TestRunner runner; +}; + +TestResults* TestRunnerFixture::s_testRunnerFixtureTestResults = NULL; + +struct MockTest : public Test +{ + MockTest(char const* testName, bool const success_, bool const assert_, int const count_ = 1) + : Test(testName) + , success(success_) + , asserted(assert_) + , count(count_) + { + m_isMockTest = true; + } + + virtual void RunImpl() const + { + TestResults* testResults = TestRunnerFixture::s_testRunnerFixtureTestResults; + + for (int i=0; i < count; ++i) + { + if (asserted) + Detail::ReportAssertEx(testResults, &m_details, "desc", "file", 0); + else if (!success) + testResults->OnTestFailure(m_details, "message"); + } + } + + bool const success; + bool const asserted; + int const count; +}; + +TEST_FIXTURE(TestRunnerFixture, TestStartIsReportedCorrectly) +{ + MockTest test("goodtest", true, false); + list.Add(&test); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(1, reporter.testRunCount); + CHECK_EQUAL("goodtest", reporter.lastStartedTest); +} + +TEST_FIXTURE(TestRunnerFixture, TestFinishIsReportedCorrectly) +{ + MockTest test("goodtest", true, false); + list.Add(&test); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(1, reporter.testFinishedCount); + CHECK_EQUAL("goodtest", reporter.lastFinishedTest); +} + +class SlowTest : public Test +{ +public: + SlowTest() : Test("slow", "somesuite", "filename", 123) {} + virtual void RunImpl() const + { + TimeHelpers::SleepMs(20); + } +}; + +TEST_FIXTURE(TestRunnerFixture, TestFinishIsCalledWithCorrectTime) +{ + SlowTest test; + list.Add(&test); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK(reporter.lastFinishedTestTime >= 0.005f && reporter.lastFinishedTestTime <= 0.050f); +} + +TEST_FIXTURE(TestRunnerFixture, FailureCountIsZeroWhenNoTestsAreRun) +{ + CHECK_EQUAL(0, runner.RunTestsIf(list, NULL, True(), 0)); + CHECK_EQUAL(0, reporter.testRunCount); + CHECK_EQUAL(0, reporter.testFailedCount); +} + +TEST_FIXTURE(TestRunnerFixture, CallsReportFailureOncePerFailingTest) +{ + MockTest test1("test", false, false); + list.Add(&test1); + MockTest test2("test", true, false); + list.Add(&test2); + MockTest test3("test", false, false); + list.Add(&test3); + + CHECK_EQUAL(2, runner.RunTestsIf(list, NULL, True(), 0)); + CHECK_EQUAL(2, reporter.testFailedCount); +} + +TEST_FIXTURE(TestRunnerFixture, TestsThatAssertAreReportedAsFailing) +{ + MockTest test("test", true, true); + list.Add(&test); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(1, reporter.testFailedCount); +} + +TEST_FIXTURE(TestRunnerFixture, AssertingTestAbortsAsSoonAsAssertIsHit) +{ + MockTest test("test", false, true, 3); + list.Add(&test); + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(1, reporter.summaryFailureCount); +} + +TEST_FIXTURE(TestRunnerFixture, ReporterNotifiedOfTestCount) +{ + MockTest test1("test", true, false); + MockTest test2("test", true, false); + MockTest test3("test", true, false); + list.Add(&test1); + list.Add(&test2); + list.Add(&test3); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(3, reporter.summaryTotalTestCount); +} + +TEST_FIXTURE(TestRunnerFixture, ReporterNotifiedOfFailedTests) +{ + MockTest test1("test", false, false, 2); + MockTest test2("test", true, false); + MockTest test3("test", false, false, 3); + list.Add(&test1); + list.Add(&test2); + list.Add(&test3); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(2, reporter.summaryFailedTestCount); +} + +TEST_FIXTURE(TestRunnerFixture, ReporterNotifiedOfFailures) +{ + MockTest test1("test", false, false, 2); + MockTest test2("test", true, false); + MockTest test3("test", false, false, 3); + list.Add(&test1); + list.Add(&test2); + list.Add(&test3); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(5, reporter.summaryFailureCount); +} + +TEST_FIXTURE(TestRunnerFixture, SlowTestPassesForHighTimeThreshold) +{ + SlowTest test; + list.Add(&test); + + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(0, reporter.testFailedCount); +} + +struct TestSuiteFixture +{ + TestSuiteFixture() + : test1("TestInDefaultSuite") + , test2("TestInOtherSuite", "OtherSuite") + , test3("SecondTestInDefaultSuite") + , runner(reporter) + { + list.Add(&test1); + list.Add(&test2); + } + + Test test1; + Test test2; + Test test3; + RecordingReporter reporter; + TestList list; + TestRunner runner; +}; + +TEST_FIXTURE(TestSuiteFixture, TestRunnerRunsAllSuitesIfNullSuiteIsPassed) +{ + runner.RunTestsIf(list, NULL, True(), 0); + CHECK_EQUAL(2, reporter.summaryTotalTestCount); +} + +TEST_FIXTURE(TestSuiteFixture,TestRunnerRunsOnlySpecifiedSuite) +{ + runner.RunTestsIf(list, "OtherSuite", True(), 0); + CHECK_EQUAL(1, reporter.summaryTotalTestCount); + CHECK_EQUAL("TestInOtherSuite", reporter.lastFinishedTest); +} + +struct RunTestIfNameIs +{ + RunTestIfNameIs(char const* name_) + : name(name_) + { + } + + bool operator()(const Test* const test) const + { + using namespace std; + return (0 == strcmp(test->m_details.testName, name)); + } + + char const* name; +}; + +TEST(TestMockPredicateBehavesCorrectly) +{ + RunTestIfNameIs predicate("pass"); + + Test pass("pass"); + Test fail("fail"); + + CHECK(predicate(&pass)); + CHECK(!predicate(&fail)); +} + +TEST_FIXTURE(TestRunnerFixture, TestRunnerRunsTestsThatPassPredicate) +{ + Test should_run("goodtest"); + list.Add(&should_run); + + Test should_not_run("badtest"); + list.Add(&should_not_run); + + runner.RunTestsIf(list, NULL, RunTestIfNameIs("goodtest"), 0); + CHECK_EQUAL(1, reporter.testRunCount); + CHECK_EQUAL("goodtest", reporter.lastStartedTest); +} + +TEST_FIXTURE(TestRunnerFixture, TestRunnerOnlyRunsTestsInSpecifiedSuiteAndThatPassPredicate) +{ + Test runningTest1("goodtest", "suite"); + Test skippedTest2("goodtest"); + Test skippedTest3("badtest", "suite"); + Test skippedTest4("badtest"); + + list.Add(&runningTest1); + list.Add(&skippedTest2); + list.Add(&skippedTest3); + list.Add(&skippedTest4); + + runner.RunTestsIf(list, "suite", RunTestIfNameIs("goodtest"), 0); + + CHECK_EQUAL(1, reporter.testRunCount); + CHECK_EQUAL("goodtest", reporter.lastStartedTest); + CHECK_EQUAL("suite", reporter.lastStartedSuite); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestTestSuite.cpp b/tests/framework/UnitTestpp/src/tests/TestTestSuite.cpp new file mode 100644 index 0000000..3b904a5 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestTestSuite.cpp @@ -0,0 +1,43 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +// We're really testing if it's possible to use the same suite in two files +// to compile and link successfuly (TestTestSuite.cpp has suite with the same name) +// Note: we are outside of the anonymous namespace +SUITE(SameTestSuite) +{ + TEST(DummyTest2) + { + } +} + diff --git a/tests/framework/UnitTestpp/src/tests/TestUnitTestPP.cpp b/tests/framework/UnitTestpp/src/tests/TestUnitTestPP.cpp new file mode 100644 index 0000000..70c12be --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestUnitTestPP.cpp @@ -0,0 +1,178 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +// These are sample tests that show the different features of the framework + +namespace { + +TEST(ValidCheckSucceeds) +{ + bool const b = true; + CHECK(b); +} + +TEST(CheckWorksWithPointers) +{ + void* p = (void *)0x100; + CHECK(p); + CHECK(p != 0); +} + +TEST(ValidCheckEqualSucceeds) +{ + int const x = 3; + int const y = 3; + CHECK_EQUAL(x, y); +} + +TEST(CheckEqualWorksWithPointers) +{ + void* p = (void *)0; + CHECK_EQUAL((void*)0, p); +} + +TEST(ValidCheckCloseSucceeds) +{ + CHECK_CLOSE(2.0f, 2.001f, 0.01f); + CHECK_CLOSE(2.001f, 2.0f, 0.01f); +} + +TEST(ArrayCloseSucceeds) +{ + float const a1[] = {1, 2, 3}; + float const a2[] = {1, 2.01f, 3}; + CHECK_ARRAY_CLOSE(a1, a2, 3, 0.1f); +} + +#ifndef UNITTEST_NO_EXCEPTIONS + +TEST(CheckThrowMacroSucceedsOnCorrectException) +{ + struct TestException {}; + CHECK_THROW(throw TestException(), TestException); +} + +TEST(CheckAssertSucceeds) +{ + CHECK_ASSERT(UnitTest::ReportAssert("desc", "file", 0)); +} + +TEST(CheckThrowMacroFailsOnMissingException) +{ + class NoThrowTest : public UnitTest::Test + { + public: + NoThrowTest() : Test("nothrow") {} + void DontThrow() const + { + } + + virtual void RunImpl() const + { + CHECK_THROW(DontThrow(), int); + } + }; + + UnitTest::TestResults results; + { + ScopedCurrentTest scopedResults(results); + + NoThrowTest test; + test.Run(); + } + + CHECK_EQUAL(1, results.GetFailureCount()); +} + +TEST(CheckThrowMacroFailsOnWrongException) +{ + class WrongThrowTest : public UnitTest::Test + { + public: + WrongThrowTest() : Test("wrongthrow") {} + virtual void RunImpl() const + { + CHECK_THROW(throw "oops", int); + } + }; + + UnitTest::TestResults results; + { + ScopedCurrentTest scopedResults(results); + + WrongThrowTest test; + test.Run(); + } + + CHECK_EQUAL(1, results.GetFailureCount()); +} + +#endif + +struct SimpleFixture +{ + SimpleFixture() + { + ++instanceCount; + } + ~SimpleFixture() + { + --instanceCount; + } + + static int instanceCount; +}; + +int SimpleFixture::instanceCount = 0; + +TEST_FIXTURE(SimpleFixture, DefaultFixtureCtorIsCalled) +{ + CHECK(SimpleFixture::instanceCount > 0); +} + +TEST_FIXTURE(SimpleFixture, OnlyOneFixtureAliveAtATime) +{ + CHECK_EQUAL(1, SimpleFixture::instanceCount); +} + +void CheckBool(const bool b) +{ + CHECK(b); +} + +TEST(CanCallCHECKOutsideOfTestFunction) +{ + CheckBool(true); +} + +} diff --git a/tests/framework/UnitTestpp/src/tests/TestXmlTestReporter.cpp b/tests/framework/UnitTestpp/src/tests/TestXmlTestReporter.cpp new file mode 100644 index 0000000..5022c9b --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/TestXmlTestReporter.cpp @@ -0,0 +1,219 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#include "stdafx.h" + +#ifndef UNITTEST_NO_DEFERRED_REPORTER + +#include "../XmlTestReporter.h" + +#include + +using namespace UnitTest; +using std::ostringstream; + +namespace +{ + +#ifndef UNITTEST_MEMORYOUTSTREAM_IS_STD_OSTRINGSTREAM + +// Overload to let MemoryOutStream accept std::string +MemoryOutStream& operator<<(MemoryOutStream& s, const std::string& value) +{ + s << value.c_str(); + return s; +} + +#endif + +struct XmlTestReporterFixture +{ + XmlTestReporterFixture() + : reporter(output) + { + } + + ostringstream output; + XmlTestReporter reporter; +}; + +TEST_FIXTURE(XmlTestReporterFixture, MultipleCharactersAreEscaped) +{ + TestDetails const details("TestName", "suite", "filename.h", 4321); + + reporter.ReportTestStart(details); + reporter.ReportFailure(details, "\"\"\'\'&&<<>>"); + reporter.ReportTestFinish(details, false, 0.1f); + reporter.ReportSummary(1, 2, 3, 0.1f); + + char const* expected = + "" + "" + "" + "" + "" + ""; + + CHECK_EQUAL(expected, output.str()); +} + +TEST_FIXTURE(XmlTestReporterFixture, OutputIsCachedUntilReportSummaryIsCalled) +{ + TestDetails const details("", "", "", 0); + + reporter.ReportTestStart(details); + reporter.ReportFailure(details, "message"); + reporter.ReportTestFinish(details, false, 1.0F); + CHECK(output.str().empty()); + + reporter.ReportSummary(1, 1, 1, 1.0f); + CHECK(!output.str().empty()); +} + +TEST_FIXTURE(XmlTestReporterFixture, EmptyReportSummaryFormat) +{ + reporter.ReportSummary(0, 0, 0, 0.1f); + + const char *expected = + "" + "" + ""; + + CHECK_EQUAL(expected, output.str()); +} + +TEST_FIXTURE(XmlTestReporterFixture, SingleSuccessfulTestReportSummaryFormat) +{ + TestDetails const details("TestName", "DefaultSuite", "", 0); + + reporter.ReportTestStart(details); + reporter.ReportSummary(1, 0, 0, 0.1f); + + const char *expected = + "" + "" + "" + ""; + + CHECK_EQUAL(expected, output.str()); +} + +TEST_FIXTURE(XmlTestReporterFixture, SingleFailedTestReportSummaryFormat) +{ + TestDetails const details("A Test", "suite", "A File", 4321); + + reporter.ReportTestStart(details); + reporter.ReportFailure(details, "A Failure"); + reporter.ReportSummary(1, 1, 1, 0.1f); + + const char *expected = + "" + "" + "" + "" + "" + ""; + + CHECK_EQUAL(expected, output.str()); +} + +TEST_FIXTURE(XmlTestReporterFixture, FailureMessageIsXMLEscaped) +{ + TestDetails const details("TestName", "suite", "filename.h", 4321); + + reporter.ReportTestStart(details); + reporter.ReportFailure(details, "\"\'&<>"); + reporter.ReportTestFinish(details, false, 0.1f); + reporter.ReportSummary(1, 1, 1, 0.1f); + + char const* expected = + "" + "" + "" + "" + "" + ""; + + CHECK_EQUAL(expected, output.str()); +} + +TEST_FIXTURE(XmlTestReporterFixture, OneFailureAndOneSuccess) +{ + TestDetails const failedDetails("FailedTest", "suite", "fail.h", 1); + reporter.ReportTestStart(failedDetails); + reporter.ReportFailure(failedDetails, "expected 1 but was 2"); + reporter.ReportTestFinish(failedDetails, false, 0.1f); + + TestDetails const succeededDetails("SucceededTest", "suite", "", 0); + reporter.ReportTestStart(succeededDetails); + reporter.ReportTestFinish(succeededDetails, true, 1.0f); + reporter.ReportSummary(2, 1, 1, 1.1f); + + char const* expected = + "" + "" + "" + "" + "" + "" + ""; + + CHECK_EQUAL(expected, output.str()); +} + +TEST_FIXTURE(XmlTestReporterFixture, MultipleFailures) +{ + TestDetails const failedDetails1("FailedTest", "suite", "fail.h", 1); + TestDetails const failedDetails2("FailedTest", "suite", "fail.h", 31); + + reporter.ReportTestStart(failedDetails1); + reporter.ReportFailure(failedDetails1, "expected 1 but was 2"); + reporter.ReportFailure(failedDetails2, "expected one but was two"); + reporter.ReportTestFinish(failedDetails1, false, 0.1f); + + reporter.ReportSummary(1, 1, 2, 1.1f); + + char const* expected = + "" + "" + "" + "" + "" + "" + ""; + + CHECK_EQUAL(expected, output.str()); +} + +} + +#endif diff --git a/tests/framework/UnitTestpp/src/tests/stdafx.cpp b/tests/framework/UnitTestpp/src/tests/stdafx.cpp new file mode 100644 index 0000000..dc9c392 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/stdafx.cpp @@ -0,0 +1,35 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +// stdafx.cpp : +// Include the standard header and generate the precompiled header. + +#include "stdafx.h" \ No newline at end of file diff --git a/tests/framework/UnitTestpp/src/tests/stdafx.h b/tests/framework/UnitTestpp/src/tests/stdafx.h new file mode 100644 index 0000000..6fa6382 --- /dev/null +++ b/tests/framework/UnitTestpp/src/tests/stdafx.h @@ -0,0 +1,48 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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. +***/ + +#pragma once + +#include "../../config.h" +#include "../../unittestpp.h" + +#include "../TestMacros.h" +#include "../CurrentTest.h" +#include "../TestReporter.h" +#include "../TestResults.h" +#include "../ReportAssert.h" +#include "../TimeHelpers.h" +#include "../ReportAssertImpl.h" + +#include "ScopedCurrentTest.h" +#include "RecordingReporter.h" + +#include \ No newline at end of file diff --git a/tests/framework/UnitTestpp/unittestpp.h b/tests/framework/UnitTestpp/unittestpp.h new file mode 100644 index 0000000..6cd7601 --- /dev/null +++ b/tests/framework/UnitTestpp/unittestpp.h @@ -0,0 +1,42 @@ +/*** +* This file is based on or incorporates material from the UnitTest++ r30 open source project. +* Microsoft is not the original author of this code but has modified it and is licensing the code under +* the MIT License. Microsoft reserves all other rights not expressly granted under the MIT License, +* whether by implication, estoppel or otherwise. +* +* UnitTest++ r30 +* +* Copyright (c) 2006 Noel Llopis and Charles Nicholson +* Portions Copyright (c) Microsoft Corporation +* +* All Rights Reserved. +* +* MIT License +* +* 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 UNITTESTPP_H +#define UNITTESTPP_H + +#include "config.h" +#include "src/TestMacros.h" +#include "src/CheckMacros.h" +#include "src/TestRunner.h" +#include "src/ReportAssert.h" +#include "src/GlobalSettings.h" + +#endif diff --git a/tests/framework/utilities/CMakeLists.txt b/tests/framework/utilities/CMakeLists.txt new file mode 100644 index 0000000..f0e8029 --- /dev/null +++ b/tests/framework/utilities/CMakeLists.txt @@ -0,0 +1,13 @@ +include_directories(include) + +if(UNIX) + set(CXX_FLAGS "${CXX_FLAGS} -include ${ODATACPP_INCLUDE_DIR}/compat/linux_compat.h") +endif() + +add_library(${LIB}utilities + os_utilities.cpp + ) + +target_link_libraries(${LIB}utilities + ${LIB}unittestpp + ) diff --git a/tests/framework/utilities/include/common_utilities_public.h b/tests/framework/utilities/include/common_utilities_public.h new file mode 100644 index 0000000..fa4fe26 --- /dev/null +++ b/tests/framework/utilities/include/common_utilities_public.h @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#if !defined(_WIN32) && !defined(__cplusplus_winrt) +#define TEST_UTILITY_API +#endif // !_WIN32 && !__cplusplus_winrt + +#ifndef TEST_UTILITY_API +#ifdef COMMONUTILITIES_EXPORTS +#define TEST_UTILITY_API __declspec(dllexport) +#else // COMMONUTILITIES_EXPORTS +#define TEST_UTILITY_API __declspec(dllimport) +#endif // COMMONUTILITIES_EXPORTS +#endif // TEST_UTILITY_API diff --git a/tests/framework/utilities/include/os_utilities.h b/tests/framework/utilities/include/os_utilities.h new file mode 100644 index 0000000..61b8ae9 --- /dev/null +++ b/tests/framework/utilities/include/os_utilities.h @@ -0,0 +1,30 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "common_utilities_public.h" + +namespace tests { namespace common { namespace utilities { + +class os_utilities +{ +public: + + static TEST_UTILITY_API void sleep(unsigned long ms); + + // Could use std::atomics but VS 2010 doesn't support it yet. + static TEST_UTILITY_API unsigned long interlocked_increment(volatile unsigned long *addend); + static TEST_UTILITY_API long interlocked_exchange(volatile long *target, long value); + +private: + os_utilities(); + os_utilities(const os_utilities &); + os_utilities & operator=(const os_utilities &); +}; + +}}} + diff --git a/tests/framework/utilities/os_utilities.cpp b/tests/framework/utilities/os_utilities.cpp new file mode 100644 index 0000000..2621a44 --- /dev/null +++ b/tests/framework/utilities/os_utilities.cpp @@ -0,0 +1,52 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "stdafx.h" + +#include "os_utilities.h" + +#ifdef WIN32 +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define NOMINMAX +#include +#else +#include +#endif + +namespace tests { namespace common { namespace utilities { + +void os_utilities::sleep(unsigned long ms) +{ +#ifdef WIN32 + Sleep(ms); +#else + usleep(ms*1000); +#endif +} + +unsigned long os_utilities::interlocked_increment(volatile unsigned long *addend) +{ +#ifdef WIN32 + return InterlockedIncrement(addend); +#elif defined(__GNUC__) + return __sync_add_and_fetch(addend, 1); +#else +#error Need to implement interlocked_increment +#endif +} + +long os_utilities::interlocked_exchange(volatile long *target, long value) +{ +#ifdef WIN32 + return InterlockedExchange(target, value); +#elif defined(__GNUC__) + return __sync_lock_test_and_set(target, value); +#else +#error Need to implement interlocked_exchange +#endif +} + +}}} diff --git a/tests/framework/utilities/stdafx.h b/tests/framework/utilities/stdafx.h new file mode 100644 index 0000000..3a5d721 --- /dev/null +++ b/tests/framework/utilities/stdafx.h @@ -0,0 +1,17 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#ifdef WIN32 + +#include "targetver.h" + +#define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers +#define NOMINMAX +#include + +#endif diff --git a/tests/framework/utilities/targetver.h b/tests/framework/utilities/targetver.h new file mode 100644 index 0000000..8d62764 --- /dev/null +++ b/tests/framework/utilities/targetver.h @@ -0,0 +1,32 @@ +/*** +* ==++== +* +* Copyright (c) Microsoft Corporation. All rights reserved. +* 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. +* +* ==--== +* =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+ +* +* targetver.h +* +* Standard VS-generated header file. +* +* =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- +****/ +#pragma once + +// Including SDKDDKVer.h defines the highest available Windows platform. + +// If you wish to build your application for a previous Windows platform, include WinSDKVer.h and +// set the _WIN32_WINNT macro to the platform you wish to support before including SDKDDKVer.h. + +#include diff --git a/tests/functional/CMakeLists.txt b/tests/functional/CMakeLists.txt new file mode 100644 index 0000000..429e998 --- /dev/null +++ b/tests/functional/CMakeLists.txt @@ -0,0 +1,31 @@ +include_directories( + ${UnitTestpp_INCLUDE_DIR} + ${Utilities_INCLUDE_DIR} + ) + +# Target +set(ODATACPP_TESTS_FUNCTIONAL odata-tests-functional) + +set(ODATACPP_TESTS_FUNCTIONAL_SOURCES + odata_tests.cpp + common_test/common_utility_test.cpp + common_test/common_nullable_test.cpp + core_test/odata_collection_value_test.cpp + core_test/odata_json_writer_test.cpp + core_test/odata_context_url_parser_test.cpp + core_test/odata_value_test.cpp + core_test/odata_json_reader_test.cpp + core_test/odata_uri_parser_test.cpp + edm_test/edm_model_reader_test.cpp + edm_test/edm_model_utility_test.cpp + ) + +set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -w") + +add_library(${ODATACPP_TESTS_FUNCTIONAL} ${ODATACPP_TESTS_FUNCTIONAL_SOURCES}) + +target_link_libraries(${ODATACPP_TESTS_FUNCTIONAL} + ${ODATACPP_LIBRARY} + unittestpp + utilities + ) diff --git a/tests/functional/common_test/common_nullable_test.cpp b/tests/functional/common_test/common_nullable_test.cpp new file mode 100644 index 0000000..be1f018 --- /dev/null +++ b/tests/functional/common_test/common_nullable_test.cpp @@ -0,0 +1,101 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/nullable.h" + +using namespace ::odata::common; + +namespace tests { namespace functional { namespace _odata { + +SUITE(common_nullable_test) +{ + +TEST(compare_same_nullable) +{ + nullable x(1); + nullable y(1); + nullable z(2); + nullable u; + nullable v; + VERIFY_ARE_EQUAL(x, y); + VERIFY_ARE_NOT_EQUAL(x, z); + VERIFY_ARE_NOT_EQUAL(x, u); + VERIFY_ARE_EQUAL(u, v); +} + +TEST(compare_different_nullable) +{ + nullable x(1); + nullable y(1); + nullable z(2); + nullable u; + nullable v; + VERIFY_ARE_NOT_EQUAL(x, y); + VERIFY_ARE_NOT_EQUAL(x, z); + VERIFY_ARE_EQUAL(u, v); + VERIFY_ARE_NOT_EQUAL(x, v); +} + +TEST(compare_nullable_with_T) +{ + nullable x(1); + nullable y; + VERIFY_ARE_EQUAL(x, 1); + VERIFY_ARE_NOT_EQUAL(x, 2); + VERIFY_ARE_EQUAL(1, x); + VERIFY_ARE_NOT_EQUAL(2, x); + VERIFY_ARE_NOT_EQUAL(y, 1); + VERIFY_ARE_NOT_EQUAL(1, y); +} + +TEST(assignment_same_nullable) +{ + nullable x(1); + nullable y; + VERIFY_IS_TRUE(x.has_value()); + VERIFY_IS_FALSE(y.has_value()); + y = x; + VERIFY_IS_TRUE(y.has_value()); + VERIFY_ARE_EQUAL(x, y); + nullable z; + y = z; + VERIFY_IS_FALSE(y.has_value()); +} + +TEST(assignment_nullable_T) +{ + nullable x; + VERIFY_IS_FALSE(x.has_value()); + x = 1; + VERIFY_IS_TRUE(x.has_value()); + VERIFY_ARE_EQUAL(x, 1); + x = 2.5; + VERIFY_IS_TRUE(x.has_value()); + VERIFY_ARE_EQUAL(x, 2); +} + +TEST(new_from_nullptr) +{ + nullable x = null_value; + VERIFY_IS_FALSE(x.has_value()); + VERIFY_IS_TRUE(x == null_value); + nullable y(1); + VERIFY_IS_TRUE(null_value != y); +} + +TEST(assignment_nullable_nullptr) +{ + nullable x(1); + VERIFY_IS_TRUE(x.has_value()); + x = null_value; + VERIFY_IS_FALSE(x.has_value()); + VERIFY_IS_TRUE(x == null_value); +} + +} + +}}} \ No newline at end of file diff --git a/tests/functional/common_test/common_utility_test.cpp b/tests/functional/common_test/common_utility_test.cpp new file mode 100644 index 0000000..bf23444 --- /dev/null +++ b/tests/functional/common_test/common_utility_test.cpp @@ -0,0 +1,315 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/utility.h" + +using namespace ::odata::edm; +using namespace ::odata::core; +using namespace ::odata::utility; + +namespace tests { namespace functional { namespace _odata { + +SUITE(common_utility_test) +{ + +TEST(strip_string_empty_test) +{ + ::odata::utility::string_t case_string = U(""); + auto ret = strip_string(case_string); + VERIFY_ARE_EQUAL(ret, U("")); +} + +TEST(strip_string_empty_quote_test) +{ + ::odata::utility::string_t case_string = U("\"\""); + auto ret = strip_string(case_string); + VERIFY_ARE_EQUAL(ret, U("")); +} + +TEST(strip_string_with_quote_test) +{ + ::odata::utility::string_t case_string = U("\"121232123\""); + auto ret = strip_string(case_string); + VERIFY_ARE_EQUAL(ret, U("121232123")); +} + +TEST(strip_string_without_quote_test) +{ + ::odata::utility::string_t case_string = U("121232123"); + auto ret = strip_string(case_string); + VERIFY_ARE_EQUAL(ret, U("121232123")); +} + +TEST(strip_string_with_single_left_quote_test) +{ + ::odata::utility::string_t case_string = U("\"121232123"); + auto ret = strip_string(case_string); + VERIFY_ARE_EQUAL(ret, U("121232123")); +} + +TEST(strip_string_with_single_right_quote_test) +{ + ::odata::utility::string_t case_string = U("121232123\""); + auto ret = strip_string(case_string); + VERIFY_ARE_EQUAL(ret, U("121232123")); +} + +TEST(split_string_empty_src_with_empty_delim_test) +{ + ::odata::utility::string_t case_string = U(""); + ::odata::utility::string_t delim_string = U(""); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 1); +} + +TEST(split_string_with_no_empty_input_test) +{ + ::odata::utility::string_t case_string = U("23123.23232.32323"); + ::odata::utility::string_t delim_string = U("."); + std::list<::odata::utility::string_t> ret; + ret.push_back(U("1")); + ret.push_back(U("2")); + ret.push_back(U("3")); + ret.push_back(U("4")); + ret.push_back(U("5")); + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 3); +} + +TEST(split_string_empty_src_with_delim_test) +{ + ::odata::utility::string_t case_string = U(""); + ::odata::utility::string_t delim_string = U(".."); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 1); +} + +TEST(split_string_src_with_empty_delim_test) +{ + ::odata::utility::string_t case_string = U("adfadfadfdas"); + ::odata::utility::string_t delim_string = U(""); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 1); +} + +TEST(split_string_src_with_delim_notfind_test) +{ + ::odata::utility::string_t case_string = U("adfadfadfdas"); + ::odata::utility::string_t delim_string = U(".."); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 1); +} + +TEST(split_string_src_with_delim_find_test) +{ + ::odata::utility::string_t case_string = U("..adf..ad..fa..dfdas.."); + ::odata::utility::string_t delim_string = U(".."); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 6); +} + +TEST(split_string_src_with_duplicated_delim_test) +{ + ::odata::utility::string_t case_string = U("...adf..ad....fa..dfdas..."); + ::odata::utility::string_t delim_string = U(".."); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 7); +} + +TEST(split_string_src_with_single_char_delim_test) +{ + ::odata::utility::string_t case_string = U("...adf..ad....fa..dfdas...f"); + ::odata::utility::string_t delim_string = U("f"); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 5); +} + +TEST(split_string_src_with_equal_delim_test) +{ + ::odata::utility::string_t case_string = U("...adf..ad....fa..dfdas...f"); + ::odata::utility::string_t delim_string = U("...adf..ad....fa..dfdas...f"); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 2); +} + +TEST(split_string_src_with_exceed_length_delim_test) +{ + ::odata::utility::string_t case_string = U("...adf..ad....fa..dfdas...f"); + ::odata::utility::string_t delim_string = U("...adf..ad....fa..dfdas...fdfdfdfdf"); + std::list<::odata::utility::string_t> ret; + + split_string(case_string, delim_string, ret); + VERIFY_ARE_EQUAL(ret.size(), 1); + VERIFY_ARE_EQUAL(ret.front(), U("...adf..ad....fa..dfdas...f")); +} + +TEST(is_relative_path_empty_root_wity_empty_path) +{ + ::odata::utility::string_t root_string = U(""); + ::odata::utility::string_t path_string = U(""); + + bool ret = is_relative_path(root_string, path_string); + VERIFY_ARE_EQUAL(ret, false); +} + +TEST(is_relative_path_empty_root) +{ + ::odata::utility::string_t root_string = U(""); + ::odata::utility::string_t path_string = U("Accounts"); + + bool ret = is_relative_path(root_string, path_string); + VERIFY_ARE_EQUAL(ret, false); +} + +TEST(is_relative_path_empty_path) +{ + ::odata::utility::string_t root_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService"); + ::odata::utility::string_t path_string = U(""); + + bool ret = is_relative_path(root_string, path_string); + VERIFY_ARE_EQUAL(ret, false); +} + +TEST(is_relative_path_valid_root_wity_full_path) +{ + ::odata::utility::string_t root_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService"); + ::odata::utility::string_t path_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts"); + + bool ret = is_relative_path(root_string, path_string); + VERIFY_ARE_EQUAL(ret, false); +} + +TEST(is_relative_path_valid_root_wity_relative_path) +{ + ::odata::utility::string_t root_string = U("http://http://odatae2etest.azurewebsites.net/cpptest/DefaultService"); + ::odata::utility::string_t path_string = U("http"); + + bool ret = is_relative_path(root_string, path_string); + VERIFY_ARE_EQUAL(ret, true); +} + +TEST(is_relative_path_valid_root_wity_uppercase_full_path) +{ + ::odata::utility::string_t root_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService"); + ::odata::utility::string_t path_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts"); + + bool ret = is_relative_path(root_string, path_string); + VERIFY_ARE_EQUAL(ret, false); +} + +TEST(print_double_zero_input) +{ + double zero = 0.0; + ::odata::utility::string_t ret = print_double(zero); + VERIFY_ARE_EQUAL(ret, U("0.0")); +} + +TEST(print_double_zero_input_2) +{ + double zero = 0; + ::odata::utility::string_t ret = print_double(zero); + VERIFY_ARE_EQUAL(ret, U("0.0")); +} + +TEST(print_double_minus_zero_input) +{ + double zero = -0.0; + ::odata::utility::string_t ret = print_double(zero); + VERIFY_ARE_EQUAL(ret, U("-0.0")); +} + +TEST(print_double_minus_zero_input_2) +{ + double zero = -0; + ::odata::utility::string_t ret = print_double(zero); + VERIFY_ARE_EQUAL(ret, U("0.0")); +} + +TEST(print_double_valid_input) +{ + double zero = 212.1234; + ::odata::utility::string_t ret = print_double(zero, 4); + VERIFY_ARE_EQUAL(ret, U("212.1234")); +} + +TEST(print_double_right_exceed_precision_input) +{ + double zero = 123123123.1111222323; + ::odata::utility::string_t ret = print_double(zero, 8); + VERIFY_ARE_EQUAL(ret, U("123123123.11112224")); +} + +TEST(print_double_set_right_precision_input) +{ + double zero = -4212.1111222323; + ::odata::utility::string_t ret = print_double(zero, 13); + VERIFY_ARE_EQUAL(ret, U("-4212.1111222322997")); +} + +TEST(print_double_zero_precision_input) +{ + double zero = -4212.1111222323; + ::odata::utility::string_t ret = print_double(zero, 0); + VERIFY_ARE_EQUAL(ret, U("-4212")); +} + +TEST(print_double_minux_precision_input) +{ + double zero = -4212.11; + ::odata::utility::string_t ret = print_double(zero, 9); + VERIFY_ARE_EQUAL(ret, U("-4212.11")); +} + +TEST(print_double_large_double_input) +{ + double zero = -12123123123213124212.11; + ::odata::utility::string_t ret = print_double(zero, 1); +#ifdef WIN32 + VERIFY_ARE_EQUAL(ret, U("-12123123123213124000.0")); +#else + VERIFY_ARE_EQUAL(ret, U("-12123123123213123584.0")); +#endif +} + +TEST(print_double_large_double_input_2) +{ + double zero = 1234567891234567.7674567611117999; + ::odata::utility::string_t ret = print_double(zero, 1); +#ifdef WIN32 + VERIFY_ARE_EQUAL(ret, U("1234567891234567.7")); +#else + VERIFY_ARE_EQUAL(ret, U("1234567891234567.8")); +#endif +} + +TEST(print_double_large_double_input_3) +{ + double zero = -12345.7674567611117999; + ::odata::utility::string_t ret = print_double(zero, 12); + VERIFY_ARE_EQUAL(ret, U("-12345.767456761112")); +} + +} + +}}} \ No newline at end of file diff --git a/tests/functional/core_test/odata_collection_value_test.cpp b/tests/functional/core_test/odata_collection_value_test.cpp new file mode 100644 index 0000000..ba27e20 --- /dev/null +++ b/tests/functional/core_test/odata_collection_value_test.cpp @@ -0,0 +1,47 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_reader.h" + +using namespace ::odata::edm; +using namespace ::odata::core; +using namespace std; + +namespace tests { namespace functional { namespace _odata { + +SUITE(odata_collection_value_test_cases) +{ +TEST(collection_of_primitive_value) +{ + shared_ptr int_type = edm_primitive_type::INT32(); + shared_ptr collection_type = std::make_shared(int_type); + shared_ptr collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(odata_primitive_value::make_primitive_value(10)); + collection_value->add_collection_value(odata_primitive_value::make_primitive_value(-10)); + + VERIFY_ARE_EQUAL(edm_type_kind_t::Collection, collection_value->get_value_type()->get_type_kind()); + + VERIFY_ARE_EQUAL(2, collection_value->get_collection_values().size()); +} + +TEST(collection_of_enum_value) +{ + shared_ptr enum_type = make_shared(U("Color"), U("namespace"), U("Edm.Int32"), false); + auto collection_type = std::make_shared(enum_type); + auto collection_value = std::make_shared(collection_type); + + collection_value->add_collection_value(make_shared(enum_type, U("Blue"))); + collection_value->add_collection_value(make_shared(enum_type, U("Red"))); + + VERIFY_ARE_EQUAL(edm_type_kind_t::Collection, collection_value->get_value_type()->get_type_kind()); + + VERIFY_ARE_EQUAL(2, collection_value->get_collection_values().size()); +} + +}}}} \ No newline at end of file diff --git a/tests/functional/core_test/odata_context_url_parser_test.cpp b/tests/functional/core_test/odata_context_url_parser_test.cpp new file mode 100644 index 0000000..3f273f6 --- /dev/null +++ b/tests/functional/core_test/odata_context_url_parser_test.cpp @@ -0,0 +1,331 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/core/odata_core.h" +#include "odata/core/odata_context_url_parser.h" +#include "odata/edm/edm_model_utility.h" + +using namespace ::odata::core; +using namespace ::odata::edm; + +namespace tests { namespace functional { namespace _odata { + +SUITE(odata_context_url_parser) +{ + +TEST(single_entity_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t single_entity_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts/$entity"); + auto return_type = context_url_parser->get_payload_content_type(single_entity_string); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(return_type->get_name(), U("Account")); +} + +TEST(entities_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _entities_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts"); + auto return_type = context_url_parser->get_payload_content_type(_entities_string); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto collection_type = std::dynamic_pointer_cast(return_type); + VERIFY_ARE_EQUAL(collection_type->get_element_type()->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(collection_type->get_element_type()->get_name(), U("Account")); +} + +TEST(derived_entity) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _entities_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People/Microsoft.Test.OData.Services.ODataWCFService.Customer/$entity"); + auto return_type = context_url_parser->get_payload_content_type(_entities_string); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(return_type->get_name(), U("Customer")); +} + +TEST(project_entity) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _entities_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Customers(PersonID,Orders)/$entity"); + auto return_type = context_url_parser->get_payload_content_type(_entities_string); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(return_type->get_name(), U("Customer")); +} + +TEST(collection_project_entities) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _entities_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Customers(PersonID,Orders)"); + auto return_type = context_url_parser->get_payload_content_type(_entities_string); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto collection_type = std::dynamic_pointer_cast(return_type); + VERIFY_ARE_EQUAL(collection_type->get_element_type()->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(collection_type->get_element_type()->get_name(), U("Customer")); +} + +TEST(collection_project_expand_entity) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _entities_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Customers(PersonID,Orders,Orders(OrderID,OrderDate))/$entity"); + auto return_type = context_url_parser->get_payload_content_type(_entities_string); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(return_type->get_name(), U("Customer")); +} + +TEST(collection_project_expand_entities) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _entities_string = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Customers(PersonID,Orders,Orders(OrderID,OrderDate))"); + auto return_type = context_url_parser->get_payload_content_type(_entities_string); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto collection_type = std::dynamic_pointer_cast(return_type); + VERIFY_ARE_EQUAL(collection_type->get_element_type()->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(collection_type->get_element_type()->get_name(), U("Customer")); +} + +TEST(single_complex_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _single_complex = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(102)/AccountInfo"); + auto return_type = context_url_parser->get_payload_content_type(_single_complex); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Complex); + VERIFY_ARE_EQUAL(return_type->get_name(), U("AccountInfo")); +} + +TEST(single_complex_test_2) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _single_complex = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Microsoft.Test.OData.Services.ODataWCFService.HomeAddress"); + auto return_type = context_url_parser->get_payload_content_type(_single_complex); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Complex); + VERIFY_ARE_EQUAL(return_type->get_name(), U("HomeAddress")); +} + +TEST(derived_complex_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _single_complex = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People(1)/HomeAddress/Microsoft.Test.OData.Services.ODataWCFService.HomeAddress"); + auto return_type = context_url_parser->get_payload_content_type(_single_complex); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Complex); + VERIFY_ARE_EQUAL(return_type->get_name(), U("HomeAddress")); +} + +TEST(single_primitive_test_double) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + + ::odata::utility::string_t _single_primitive = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Edm.Double"); + auto return_type = context_url_parser->get_payload_content_type(_single_primitive); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(return_type->get_name(), U("Edm.Double")); +} + +TEST(single_primitive_test_binary) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + + ::odata::utility::string_t _single_primitive = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Edm.Binary"); + auto return_type = context_url_parser->get_payload_content_type(_single_primitive); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(return_type->get_name(), U("Edm.Binary")); +} + +TEST(collection_of_primitive_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _multiple_primitive = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People(2)/Numbers"); + auto return_type = context_url_parser->get_payload_content_type(_multiple_primitive); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(element_type->get_name(), U("Edm.String")); +} + +TEST(collection_of_primitive_test_string) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _multiple_primitive = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Collection(Edm.String)"); + auto return_type = context_url_parser->get_payload_content_type(_multiple_primitive); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(element_type->get_name(), U("Edm.String")); +} + +TEST(collection_of_primitive_test_int32) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _multiple_primitive = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Collection(Edm.Int32)"); + auto return_type = context_url_parser->get_payload_content_type(_multiple_primitive); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(element_type->get_name(), U("Edm.Int32")); +} + +TEST(collection_of_primitive_test_single) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _multiple_primitive = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Collection(Edm.Single)"); + auto return_type = context_url_parser->get_payload_content_type(_multiple_primitive); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(element_type->get_name(), U("Edm.Single")); +} + +TEST(single_enum_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _text_url = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/SkinColor"); + auto return_type = context_url_parser->get_payload_content_type(_text_url); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Enum); +} + +TEST(collection_of_enum_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _text_url = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/CoverColors"); + auto return_type = context_url_parser->get_payload_content_type(_text_url); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Enum); +} + +TEST(single_entity_with_single_navigation_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _text_url = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyGiftCard/$entity"); + auto return_type = context_url_parser->get_payload_content_type(_text_url); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(return_type->get_name(), U("GiftCard")); +} + +TEST(single_entity_with_collection_of_navigation_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _text_url = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments"); + auto return_type = context_url_parser->get_payload_content_type(_text_url); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(element_type->get_name(), U("PaymentInstrument")); +} + +TEST(single_entity_with_collection_of_navigation_and_select_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _text_url = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments(PaymentInstrumentID,FriendlyName)"); + auto return_type = context_url_parser->get_payload_content_type(_text_url); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(element_type->get_name(), U("PaymentInstrument")); +} + +TEST(navigation_of_navigation_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto context_url_parser = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(context_url_parser); + ::odata::utility::string_t _text_url = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments(101901)/BillingStatements"); + auto return_type = context_url_parser->get_payload_content_type(_text_url); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto element_type = edm_model_utility::get_collection_element_type(return_type); + VERIFY_ARE_EQUAL(element_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(element_type->get_name(), U("Statement")); +} + +} + +}}} \ No newline at end of file diff --git a/tests/functional/core_test/odata_json_reader_test.cpp b/tests/functional/core_test/odata_json_reader_test.cpp new file mode 100644 index 0000000..fd5dc1b --- /dev/null +++ b/tests/functional/core_test/odata_json_reader_test.cpp @@ -0,0 +1,1284 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/core/odata_core.h" +#include "odata/core/odata_json_reader_minimal.h" + + +using namespace ::odata::core; +using namespace ::odata::edm; + +namespace tests { namespace functional { namespace _odata { + +static std::shared_ptr get_json_reader() +{ + auto model = get_test_model(); + if (model) + { + return std::make_shared(model, g_service_root_url); + } + + return nullptr; +} + +SUITE(odata_json_reader) +{ + +TEST(single_entity_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts/$entity\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)\", \ + \"AccountID\":101,\"Country\":\"US\",\"AccountInfo\":{\"FirstName\":\"Alex\",\"LastName\":\"Green\"}}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Entity ,return_value->get_value_type()->get_type_kind()); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)"), entity_value->get_edit_link().to_string()); + int account_id = 0; + VERIFY_ARE_EQUAL(entity_value->try_get(U("AccountID"), account_id), true); + VERIFY_ARE_EQUAL(account_id, 101); + std::shared_ptr property_value; + VERIFY_ARE_EQUAL(entity_value->try_get(U("AccountInfo"), property_value), true); + VERIFY_IS_NOT_NULL(property_value); + auto complex_value = std::dynamic_pointer_cast(property_value); + VERIFY_IS_NOT_NULL(complex_value); + ::odata::utility::string_t first_name; + VERIFY_ARE_EQUAL(complex_value->try_get(U("FirstName"), first_name), true); + VERIFY_ARE_EQUAL(first_name, U("Alex")); +} + +TEST(entities_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Departments\", \ + \"value\":[{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Departments(1)\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Departments(1)\", \ + \"DepartmentID\":1,\"Name\":\"D1\"},{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Departments(2)\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Departments(2)\", \ + \"DepartmentID\":2,\"Name\":\"D2\"}]}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Collection, return_value->get_value_type()->get_type_kind()); + + auto collection_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(collection_value); + auto collection_type = std::dynamic_pointer_cast(collection_value->get_value_type()); + VERIFY_IS_NOT_NULL(collection_type); + VERIFY_ARE_EQUAL(edm_type_kind_t::Entity, collection_type->get_element_type()->get_type_kind()); + + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 2); + + auto entity_value = std::dynamic_pointer_cast(entity_values[1]); + VERIFY_IS_NOT_NULL(entity_value); + + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Departments(2)"), entity_value->get_edit_link().to_string()); + ::odata::utility::string_t name; + VERIFY_ARE_EQUAL(entity_value->try_get(U("Name"), name), true); + VERIFY_ARE_EQUAL(name, U("D2")); + int32_t id; + VERIFY_ARE_EQUAL(entity_value->try_get(U("DepartmentID"), id), true); + VERIFY_ARE_EQUAL(id, 2); +} + +TEST(derived_entity_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People\", \ + \"value\":[{\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.Customer\",\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(1)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(1)/Microsoft.Test.OData.Services.ODataWCFService.Customer\",\"PersonID\":1,\"FirstName\":\"Bob\",\"LastName\":\"Cat\",\"MiddleName\":null,\"HomeAddress\":{\"Street\":\"1 Microsoft Way\",\"City\":\"London\",\"PostalCode\":\"98052\"},\"Home\":{\"type\":\"Point\",\"coordinates\":[23.1,32.1],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}},\"Numbers\":[\"111-111-1111\"],\"Emails\":[\"abc@abc.com\"],\"City\":\"London\",\"Birthday\":\"1957-04-03T00:00:00Z\",\"TimeBetweenLastTwoOrders\":\"PT0.0000001S\"}, \ + {\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.Customer\",\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(2)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(2)/Microsoft.Test.OData.Services.ODataWCFService.Customer\",\"PersonID\":2,\"FirstName\":\"Jill\",\"LastName\":\"Jones\",\"MiddleName\":null,\"HomeAddress\":null,\"Home\":{\"type\":\"Point\",\"coordinates\":[161.8,15.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}},\"Numbers\":[],\"Emails\":[],\"City\":\"Sydney\",\"Birthday\":\"1983-01-15T00:00:00Z\",\"TimeBetweenLastTwoOrders\":\"PT0.0000002S\"}, \ + {\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.Employee\",\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(3)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(3)/Microsoft.Test.OData.Services.ODataWCFService.Employee\",\"PersonID\":3,\"FirstName\":\"Jacob\",\"LastName\":\"Zip\",\"MiddleName\":null,\"HomeAddress\":null,\"Home\":{\"type\":\"Point\",\"coordinates\":[161.8,15.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}},\"Numbers\":[\"333-333-3333\"],\"Emails\":[null],\"DateHired\":\"2010-12-13T00:00:00Z\",\"Office\":{\"type\":\"Point\",\"coordinates\":[162.0,15.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}}, \ + {\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.Employee\",\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(4)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(4)/Microsoft.Test.OData.Services.ODataWCFService.Employee\",\"PersonID\":4,\"FirstName\":\"Elmo\",\"LastName\":\"Rogers\",\"MiddleName\":null,\"HomeAddress\":null,\"Home\":{\"type\":\"Point\",\"coordinates\":[-61.8,-15.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}},\"Numbers\":[\"444-444-4444\",\"555-555-5555\",\"666-666-6666\"],\"Emails\":[\"def@def.org\",\"lmn@lmn.com\"],\"DateHired\":\"2008-03-27T00:00:00Z\",\"Office\":{\"type\":\"Point\",\"coordinates\":[-62.0,-15.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}}, \ + {\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(5)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/People(5)\",\"PersonID\":5,\"FirstName\":\"Peter\",\"LastName\":\"Bee\",\"MiddleName\":null,\"HomeAddress\":null,\"Home\":{\"type\":\"Point\",\"coordinates\":[-261.8,-16.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}},\"Numbers\":[\"555-555-5555\"],\"Emails\":[\"def@test.msn\"]}]}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 5); + + auto entity_value_1 = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_IS_NOT_NULL(entity_value_1); + auto entity_type_1 = std::dynamic_pointer_cast(entity_value_1->get_value_type()); + VERIFY_IS_NOT_NULL(entity_type_1); + VERIFY_ARE_EQUAL(entity_type_1->get_name(), U("Customer")); + ::odata::utility::string_t city_1; + VERIFY_ARE_EQUAL(entity_value_1->try_get(U("City"), city_1), true); + VERIFY_ARE_EQUAL(city_1, U("London")); + std::shared_ptr _value; + entity_value_1->get_property_value(U("HomeAddress"), _value); + auto complex_value_1 = std::dynamic_pointer_cast(_value); + VERIFY_IS_NOT_NULL(complex_value_1); + ::odata::utility::string_t street_1; + VERIFY_ARE_EQUAL(complex_value_1->try_get(U("City"), city_1), true); + VERIFY_ARE_EQUAL(city_1, U("London")); + VERIFY_ARE_EQUAL(complex_value_1->try_get(U("Street"), street_1), true); + VERIFY_ARE_EQUAL(street_1, U("1 Microsoft Way")); + + auto entity_value_2 = std::dynamic_pointer_cast(entity_values[2]); + VERIFY_IS_NOT_NULL(entity_value_2); + auto entity_type_2 = std::dynamic_pointer_cast(entity_value_2->get_value_type()); + VERIFY_IS_NOT_NULL(entity_type_2); + VERIFY_ARE_EQUAL(entity_type_2->get_name(), U("Employee")); + ::odata::utility::datetime dt; + VERIFY_ARE_EQUAL(entity_value_2->try_get(U("DateHired"), dt), true); + VERIFY_ARE_EQUAL(dt.to_string(::odata::utility::datetime::date_format::ISO_8601), U("2010-12-13T00:00:00Z")); +} + +TEST(single_complex_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto json_reader = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/AccountInfo\", \ + \"FirstName\":\"Alex\",\"LastName\":\"Green\"}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Complex, return_value->get_value_type()->get_type_kind()); + + auto complex_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(complex_value); + ::odata::utility::string_t first_name, last_name; + VERIFY_ARE_EQUAL(complex_value->try_get(U("FirstName"), first_name), true); + VERIFY_ARE_EQUAL(first_name, U("Alex")); + VERIFY_ARE_EQUAL(complex_value->try_get(U("LastName"), last_name), true); + VERIFY_ARE_EQUAL(last_name, U("Green")); + +} + +TEST(derived_complex_test) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto json_reader = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/VIPAccountInfo\", \ + \"FirstName\":\"Alex\",\"LastName\":\"Green\",\"VIP_Info\":\"Gold\"}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Complex, return_value->get_value_type()->get_type_kind()); + + auto complex_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(complex_value); + ::odata::utility::string_t first_name, last_name, vip_info; + VERIFY_ARE_EQUAL(complex_value->try_get(U("FirstName"), first_name), true); + VERIFY_ARE_EQUAL(first_name, U("Alex")); + VERIFY_ARE_EQUAL(complex_value->try_get(U("LastName"), last_name), true); + VERIFY_ARE_EQUAL(last_name, U("Green")); + VERIFY_ARE_EQUAL(complex_value->try_get(U("VIP_Info"), vip_info), true); + VERIFY_ARE_EQUAL(vip_info, U("Gold")); +} + +TEST(derived_complex_test_2) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + auto json_reader = std::make_shared(model, g_service_root_url); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/AccountInfo\", \ + \"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.VIPAccountInfo\",\"FirstName\":\"Alex\",\"LastName\":\"Green\",\"VIP_Info\":\"Gold\"}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Complex, return_value->get_value_type()->get_type_kind()); + + auto complex_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(complex_value); + ::odata::utility::string_t first_name, last_name, vip_info; + VERIFY_ARE_EQUAL(complex_value->try_get(U("FirstName"), first_name), true); + VERIFY_ARE_EQUAL(first_name, U("Alex")); + VERIFY_ARE_EQUAL(complex_value->try_get(U("LastName"), last_name), true); + VERIFY_ARE_EQUAL(last_name, U("Green")); + VERIFY_ARE_EQUAL(complex_value->try_get(U("VIP_Info"), vip_info), true); + VERIFY_ARE_EQUAL(vip_info, U("Gold")); + auto complex_type = complex_value->get_value_type(); + VERIFY_IS_NOT_NULL(complex_type); + VERIFY_ARE_EQUAL(complex_type->get_name(), U("VIPAccountInfo")); +} + +TEST(single_primitive_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/AccountInfo/FirstName\", \ + \"value\":\"Alex\"}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Primitive, return_value->get_value_type()->get_type_kind()); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(primitive_value); + VERIFY_ARE_EQUAL(primitive_value->to_string(), U("Alex")); +} + +TEST(collection_of_primitive_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People(4)/Numbers\", \ + \"value\":[\"444-444-4444\",\"555-555-5555\",\"666-666-6666\"]}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& primitive_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(primitive_values.size(), 3); + + auto primitive_value = std::dynamic_pointer_cast(primitive_values[0]); + VERIFY_IS_NOT_NULL(primitive_value); + VERIFY_ARE_EQUAL(primitive_value->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->to_string(), U("444-444-4444")); + primitive_value = std::dynamic_pointer_cast(primitive_values[1]); + VERIFY_IS_NOT_NULL(primitive_value); + VERIFY_ARE_EQUAL(primitive_value->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->to_string(), U("555-555-5555")); + primitive_value = std::dynamic_pointer_cast(primitive_values[2]); + VERIFY_IS_NOT_NULL(primitive_value); + VERIFY_ARE_EQUAL(primitive_value->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->to_string(), U("666-666-6666")); +} + +TEST(json_primitive_int16) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/TestInt16\", \ + \"value\":32}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int16); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), 32); +} + +TEST(json_primitive_int16_max) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/TestInt16\", \ + \"value\":32767}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int16); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), 32767); +} + +TEST(json_primitive_int16_min) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/TestInt16\", \ + \"value\":-32768}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int16); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), -32768); +} + +TEST(json_primitive_int32) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/ProductID\", \ + \"value\":5}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int32); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), 5); +} + +TEST(json_primitive_int32_max) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/ProductID\", \ + \"value\":2147483647}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int32); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), 2147483647); +} + +TEST(json_primitive_int32_min) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/ProductID\", \ + \"value\":-2147483648}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int32); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), -2147483648); +} + +TEST(json_primitive_int64) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Company/Revenue\", \ + \"value\":100000}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int64); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), 100000); +} + +TEST(json_primitive_int64_max) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Company/Revenue\", \ + \"value\":9223372036854775807}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int64); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), 9223372036854775807); +} + +TEST(json_primitive_int64_min) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Company/Revenue\", \ + \"value\":-9223372036854775808}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int64); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), (int64_t)-9223372036854775808); +} + +TEST(json_primitive_bool) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/Discontinued\", \ + \"value\":false}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Boolean); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), false); +} + +TEST(json_primitive_binary) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/TestBinary\", \ + \"value\":\"UUJBQkFDWDEyKjEyMzQ1Njc4OTBhYmNkZQ==\"}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Binary); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + auto binary_output = primitive_value->as>(); + VERIFY_ARE_EQUAL(binary_output.size(), 25); + VERIFY_ARE_EQUAL(binary_output[0], 'Q'); + VERIFY_ARE_EQUAL(binary_output[7], '1'); +} + +TEST(json_primitive_sbyte) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/TestSByte\", \ + \"value\":45}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::SByte); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), 45); +} + +TEST(json_primitive_byte) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/TestByte\", \ + \"value\":-23}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Byte); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as(), -23); +} + +TEST(json_primitive_double) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyGiftCard/Amount\", \ + \"value\":123.3434}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Double); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_IS_TRUE(abs(primitive_value->as() - 123.3434) < 0.000001); +} + +TEST(json_primitive_float) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/TestFloat\", \ + \"value\":-123.4}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Single); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_IS_TRUE(abs(primitive_value->as() - -123.4) < 0.00001); +} + +TEST(json_primitive_duration) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Customers(2)/TimeBetweenLastTwoOrders\", \ + \"value\":\"PT1H38M23S\"}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Duration); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as<::odata::utility::seconds>().count(), 5903); +} + +TEST(json_primitive_datetime) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People(3)/Microsoft.Test.OData.Services.ODataWCFService.Employee/DateHired\", \ + \"value\":\"2010-12-13T00:00:00Z\"}"); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto primitive_value = std::dynamic_pointer_cast(return_value); + auto primitive_type = std::dynamic_pointer_cast(primitive_value->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::DateTimeOffset); + VERIFY_ARE_EQUAL(primitive_type->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as<::odata::utility::datetime>().to_string(::odata::utility::datetime::ISO_8601), U("2010-12-13T00:00:00Z")); +} + +TEST(collection_of_primitive_with_single_return_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People(4)/Numbers\", \ + \"value\":[\"111-11-1111\"]}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& primitive_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(primitive_values.size(), 1); + + auto primitive_value = std::dynamic_pointer_cast(primitive_values[0]); + VERIFY_IS_NOT_NULL(primitive_value); + VERIFY_ARE_EQUAL(primitive_value->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + VERIFY_ARE_EQUAL(primitive_value->as<::odata::utility::string_t>(), U("111-11-1111")); +} + +TEST(collection_of_primitive_with_empty_return_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#People(4)/Numbers\", \ + \"value\":[]}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& primitive_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(primitive_values.size(), 0); +} + +TEST(single_enum_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/SkinColor\",\"value\":\"Red\"}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + + auto enum_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(enum_value); + VERIFY_ARE_EQUAL(enum_value->get_value_type()->get_type_kind(), edm_type_kind_t::Enum); + VERIFY_ARE_EQUAL(enum_value->to_string(), U("Red")); +} + +TEST(collection_of_enum_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/CoverColors\", \ + \"value\":[\"Green\",\"Blue\",\"Blue\"]}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& enum_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(enum_values.size(), 3); + + auto enum_value = std::dynamic_pointer_cast(enum_values[0]); + VERIFY_IS_NOT_NULL(enum_value); + VERIFY_ARE_EQUAL(enum_value->get_value_type()->get_type_kind(), edm_type_kind_t::Enum); + VERIFY_ARE_EQUAL(enum_value->to_string(), U("Green")); + enum_value = std::dynamic_pointer_cast(enum_values[1]); + VERIFY_IS_NOT_NULL(enum_value); + VERIFY_ARE_EQUAL(enum_value->get_value_type()->get_type_kind(), edm_type_kind_t::Enum); + VERIFY_ARE_EQUAL(enum_value->to_string(), U("Blue")); + enum_value = std::dynamic_pointer_cast(enum_values[2]); + VERIFY_IS_NOT_NULL(enum_value); + VERIFY_ARE_EQUAL(enum_value->get_value_type()->get_type_kind(), edm_type_kind_t::Enum); + VERIFY_ARE_EQUAL(enum_value->to_string(), U("Blue")); +} + +TEST(collection_of_enum_with_single_return_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/CoverColors\", \ + \"value\":[\"Green\"]}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& enum_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(enum_values.size(), 1); + + auto enum_value = std::dynamic_pointer_cast(enum_values[0]); + VERIFY_IS_NOT_NULL(enum_value); + VERIFY_ARE_EQUAL(enum_value->get_value_type()->get_type_kind(), edm_type_kind_t::Enum); + VERIFY_ARE_EQUAL(enum_value->to_string(), U("Green")); + +} + +TEST(collection_of_enum_with_empty_return_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(5)/CoverColors\", \ + \"value\":[]}" + ); + + odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& enum_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(enum_values.size(), 0); +} + +TEST(singleton_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#DefaultStoredPI\", \ + \"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/DefaultStoredPI\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/DefaultStoredPI\", \ + \"StoredPIID\":800,\"PIName\":\"The Default Stored PI\",\"PIType\":\"CreditCard\",\"CreatedDate\":\"2013-12-31T00:00:00Z\"}" + ); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Entity ,return_value->get_value_type()->get_type_kind()); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/DefaultStoredPI"), entity_value->get_edit_link().to_string()); + + int storedpid = 0; + VERIFY_ARE_EQUAL(entity_value->try_get(U("StoredPIID"), storedpid), true); + VERIFY_ARE_EQUAL(storedpid, 800); + ::odata::utility::string_t pinname; + VERIFY_ARE_EQUAL(entity_value->try_get(U("PIName"), pinname), true); + VERIFY_ARE_EQUAL(pinname, U("The Default Stored PI")); + ::odata::utility::string_t pitype; + VERIFY_ARE_EQUAL(entity_value->try_get(U("PIType"), pitype), true); + VERIFY_ARE_EQUAL(pitype, U("CreditCard")); + ::odata::utility::datetime createdate; + VERIFY_ARE_EQUAL(entity_value->try_get(U("CreatedDate"), createdate), true); + VERIFY_ARE_EQUAL(createdate.to_string(::odata::utility::datetime::date_format::ISO_8601), U("2013-12-31T00:00:00Z")); +} + +TEST(single_entity_with_single_navigation_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyGiftCard/$entity\", \ + \"GiftCardID\":301,\"GiftCardNO\":\"AAA123A\",\"Amount\":19.9,\"ExperationDate\":\"2013-12-30T00:00:00Z\"}" + ); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + VERIFY_IS_NOT_NULL(return_value); + VERIFY_ARE_EQUAL(edm_type_kind_t::Entity ,return_value->get_value_type()->get_type_kind()); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyGiftCard"), entity_value->get_edit_link().to_string()); + + int cardid = 0; + VERIFY_ARE_EQUAL(entity_value->try_get(U("GiftCardID"), cardid), true); + VERIFY_ARE_EQUAL(cardid, 301); + ::odata::utility::string_t cardno; + VERIFY_ARE_EQUAL(entity_value->try_get(U("GiftCardNO"), cardno), true); + VERIFY_ARE_EQUAL(cardno, U("AAA123A")); + double amount; + VERIFY_ARE_EQUAL(entity_value->try_get(U("Amount"), amount), true); + VERIFY_ARE_EQUAL(amount, 19.9); + ::odata::utility::datetime experationdate; + VERIFY_ARE_EQUAL(entity_value->try_get(U("ExperationDate"), experationdate), true); + VERIFY_ARE_EQUAL(experationdate.to_string(::odata::utility::datetime::date_format::ISO_8601), U("2013-12-30T00:00:00Z")); +} + +TEST(single_entity_with_collection_of_navigation_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments\", \ + \"value\":[{\"PaymentInstrumentID\":101901,\"FriendlyName\":\"101 first PI\",\"CreatedDate\":\"2012-11-01T00:00:00Z\"}, \ + {\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI\",\"PaymentInstrumentID\":101902,\"FriendlyName\": \ + \"101 frist credit PI\",\"CreatedDate\":\"2012-11-01T00:00:00Z\",\"CardNumber\":\"6000000000000000\",\"CVV\":\"234\",\"HolderName\":\"Alex\", \ + \"Balance\":100.0,\"ExperationDate\":\"2022-11-01T00:00:00Z\"}, \ + {\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI\",\"PaymentInstrumentID\":101903, \ + \"FriendlyName\":\"101 second credit PI\",\"CreatedDate\":\"2012-11-01T00:00:00Z\",\"CardNumber\":\"8000000000000000\",\"CVV\":\"012\", \ + \"HolderName\":\"James\",\"Balance\":300.0,\"ExperationDate\":\"2022-10-02T00:00:00Z\"}]}" + ); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 3); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments(101901)"), entity_value->get_edit_link().to_string()); + int payid = 0; + VERIFY_ARE_EQUAL(entity_value->try_get(U("PaymentInstrumentID"), payid), true); + VERIFY_ARE_EQUAL(payid, 101901); + entity_value = std::dynamic_pointer_cast(entity_values[1]); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(entity_value->try_get(U("PaymentInstrumentID"), payid), true); + VERIFY_ARE_EQUAL(payid, 101902); + entity_value = std::dynamic_pointer_cast(entity_values[2]); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(entity_value->try_get(U("PaymentInstrumentID"), payid), true); + VERIFY_ARE_EQUAL(payid, 101903); +} + +TEST(collection_of_entity_with_single_navigation_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Employees\", \ + \"value\":[{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Employees(PersonID=3)\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Employees(PersonID=3)\", \ + \"PersonID\":3,\"FirstName\":\"Jacob\",\"LastName\":\"Zip\",\"MiddleName\":null,\"HomeAddress\":null,\"Home\":{\"type\":\"Point\", \ + \"coordinates\":[161.8,15.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}},\"Numbers\":[\"333-333-3333\"], \ + \"Emails\":[null],\"DateHired\":\"2010-12-13T00:00:00Z\",\"Office\":{\"type\":\"Point\",\"coordinates\":[162.0,15.0],\"crs\":{\"type\": \ + \"name\",\"properties\":{\"name\":\"EPSG:4326\"}}},\"Company\":{\"@odata.id\": \ + \"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Company\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Company\", \ + \"CompanyID\":0,\"CompanyCategory\":\"IT\",\"Revenue\":100000,\"Name\":\"MS\",\"Address\":{\"Street\": \ + \"1 Microsoft Way\",\"City\":\"Redmond\",\"PostalCode\":\"98052\"}}},{\"@odata.id\": \ + \"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Employees(PersonID=4)\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Employees(PersonID=4)\", \ + \"PersonID\":4,\"FirstName\":\"Elmo\",\"LastName\":\"Rogers\",\"MiddleName\":null,\"HomeAddress\":null,\"Home\": \ + {\"type\":\"Point\",\"coordinates\":[-61.8,-15.0],\"crs\":{\"type\":\"name\",\"properties\":{\"name\":\"EPSG:4326\"}}}, \ + \"Numbers\":[\"444-444-4444\",\"555-555-5555\",\"666-666-6666\"],\"Emails\":[\"def@def.org\",\"lmn@lmn.com\"], \ + \"DateHired\":\"2008-03-27T00:00:00Z\",\"Office\":{\"type\":\"Point\",\"coordinates\":[-62.0,-15.0],\"crs\":{\"type\":\"name\",\"properties\": \ + {\"name\":\"EPSG:4326\"}}},\"Company\":{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Company\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Company\",\"CompanyID\":0,\"CompanyCategory\": \ + \"IT\",\"Revenue\":100000,\"Name\":\"MS\",\"Address\":{\"Street\":\"1 Microsoft Way\",\"City\":\"Redmond\",\"PostalCode\":\"98052\"}}}]}" + ); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 2); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Employees(PersonID=3)"), entity_value->get_edit_link().to_string()); + std::shared_ptr property_value; + auto ok = entity_value->get_property_value(U("Company"), property_value); + auto contained_value = std::dynamic_pointer_cast(property_value); + VERIFY_IS_NOT_NULL(contained_value); + VERIFY_ARE_EQUAL(contained_value->properties().size(), 6); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Company"), contained_value->get_edit_link().to_string()); +} + +TEST(collection_of_entity_with_collection_of_navigation_test) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts\",\"value\":[{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)\",\"AccountID\":101,\"Country\":\"US\",\"AccountInfo\":{\"FirstName\":\"Alex\",\"LastName\":\"Green\"},\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments\",\"MyPaymentInstruments\":[{\"PaymentInstrumentID\":101901,\"FriendlyName\":\"101 first PI\",\"CreatedDate\":\"2012-11-01T00:00:00Z\"},{\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI\",\"PaymentInstrumentID\":101902,\"FriendlyName\":\"101 frist credit PI\",\"CreatedDate\":\"2012-11-01T00:00:00Z\",\"CardNumber\":\"6000000000000000\",\"CVV\":\"234\",\"HolderName\":\"Alex\",\"Balance\":100.0,\"ExperationDate\":\"2022-11-01T00:00:00Z\"},{\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI\",\"PaymentInstrumentID\":101903,\"FriendlyName\":\"101 second credit PI\",\"CreatedDate\":\"2012-11-01T00:00:00Z\",\"CardNumber\":\"8000000000000000\",\"CVV\":\"012\",\"HolderName\":\"James\",\"Balance\":300.0,\"ExperationDate\":\"2022-10-02T00:00:00Z\"}]},{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(102)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(102)\",\"AccountID\":102,\"Country\":\"GB\",\"AccountInfo\":{\"FirstName\":\"James\",\"LastName\":\"Bunder\"},\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(102)/MyPaymentInstruments\",\"MyPaymentInstruments\":[]},{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(103)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(103)\",\"AccountID\":103,\"Country\":\"CN\",\"AccountInfo\":{\"FirstName\":\"Adam\",\"LastName\":\"Homes\"},\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(103)/MyPaymentInstruments\",\"MyPaymentInstruments\":[{\"PaymentInstrumentID\":103901,\"FriendlyName\":\"103 frist PI\",\"CreatedDate\":\"2013-10-01T00:00:00Z\"},{\"PaymentInstrumentID\":103902,\"FriendlyName\":\"103 second PI\",\"CreatedDate\":\"2013-01-01T00:00:00Z\"},{\"PaymentInstrumentID\":103905,\"FriendlyName\":\"103 new PI\",\"CreatedDate\":\"2013-10-29T00:00:00Z\"},{\"PaymentInstrumentID\":101910,\"FriendlyName\":\"103 backup PI\",\"CreatedDate\":\"2013-06-15T00:00:00Z\"}]},{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(104)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(104)\",\"AccountID\":104,\"Country\":\"CN\",\"AccountInfo\":{\"FirstName\":\"Adrian\",\"LastName\":\"Green\"},\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(104)/MyPaymentInstruments\",\"MyPaymentInstruments\":[]},{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(105)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(105)\",\"AccountID\":105,\"Country\":\"US\",\"AccountInfo\":{\"FirstName\":\"Lily\",\"LastName\":\"Green\"},\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(105)/MyPaymentInstruments\",\"MyPaymentInstruments\":[]},{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(106)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(106)\",\"AccountID\":106,\"Country\":\"CN\",\"AccountInfo\":{\"FirstName\":\"Allen\",\"LastName\":\"Ivorson\"},\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(106)/MyPaymentInstruments\",\"MyPaymentInstruments\":[]},{\"@odata.id\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(107)\",\"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(107)\",\"AccountID\":107,\"Country\":\"FR\",\"AccountInfo\":{\"FirstName\":\"Albert\",\"LastName\":\"Ivorson\"},\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(107)/MyPaymentInstruments\",\"MyPaymentInstruments\":[]}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 7); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)"), entity_value->get_edit_link().to_string()); + std::shared_ptr property_value; + auto ok = entity_value->get_property_value(U("MyPaymentInstruments"), property_value); + auto contained_values = std::dynamic_pointer_cast(property_value); + VERIFY_IS_NOT_NULL(contained_values); + VERIFY_ARE_EQUAL(contained_values->get_collection_values().size(), 3); + auto contained_value = std::dynamic_pointer_cast(contained_values->get_collection_values()[0]); + VERIFY_IS_NOT_NULL(contained_value); + VERIFY_ARE_EQUAL(contained_value->properties().size(), 3); + VERIFY_ARE_EQUAL(U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments(101901)"), contained_value->get_edit_link().to_string()); + +} + + +// @editLink value are all removed in edit link tests +//http://services.odata.org/V4/Northwind/Northwind.svc/Products +TEST(edit_link_entity_set) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(ProductID)\", \ + \"value\":[{\"ProductID\":1},{\"ProductID\":2}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 2); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Products(1)")); + + entity_value = std::dynamic_pointer_cast(entity_values[1]); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Products(2)")); +} + +TEST(edit_link_entity_relative_path) +{ + ::odata::utility::string_t _entity_payload = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts/$entity\", \ + \"@odata.editLink\":\"Accounts(101)\", \ + \"AccountID\":101,\"Country\":\"%0A%E4%B8%AD%E5%8D%8E%E4%BA%BA%E6%B0%91%E5%85%B1%E5%92%8C%E5%9B%BD\",\"AccountInfo\":{\"FirstName\":\"Alex\",\"LastName\":\"Green\"}}" + ); + + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)")); +} + +//http://services.odata.org/V4/Northwind/Northwind.svc/Products(1) +TEST(edit_link_entity_set_with_key) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products(ProductID)/$entity\", \"ProductID\":1}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Products(1)")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/LabourUnion +TEST(edit_link_singleton) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#LabourUnion\",\"LabourUnionID\":0,\"Name\":\"MS Labour Union\"}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/LabourUnion")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts?$expand=MyPaymentInstruments +TEST(edit_link_contained_navigation) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(AccountID,MyPaymentInstruments,MyPaymentInstruments(PaymentInstrumentID))\", \ + \"value\":[{\"AccountID\":101,\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments(PaymentInstrumentID)\",\"MyPaymentInstruments\":[{\"PaymentInstrumentID\":101901}]}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 1); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_IS_NOT_NULL(entity_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)")); + + std::shared_ptr property_value; + auto ok = entity_value->get_property_value(U("MyPaymentInstruments"), property_value); + auto contained_values = std::dynamic_pointer_cast(property_value); + auto contained_value = std::dynamic_pointer_cast(contained_values->get_collection_values()[0]); + VERIFY_ARE_EQUAL(contained_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments(101901)")); +} + +TEST(edit_link_contained_navigation_derived_type) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(AccountID,MyPaymentInstruments,MyPaymentInstruments(PaymentInstrumentID))\", \ + \"value\":[{\"AccountID\":101,\"MyPaymentInstruments@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments(PaymentInstrumentID)\",\"MyPaymentInstruments\":[{\"@odata.type\": \"#Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI\",\"PaymentInstrumentID\":101901}]}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 1); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)")); + + std::shared_ptr property_value; + auto ok = entity_value->get_property_value(U("MyPaymentInstruments"), property_value); + auto contained_values = std::dynamic_pointer_cast(property_value); + auto contained_value = std::dynamic_pointer_cast(contained_values->get_collection_values()[0]); + VERIFY_ARE_EQUAL(contained_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments(101901)/Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments +TEST(edit_link_contained_navigation_property_derived_type) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments(PaymentInstrumentID)\", \ + \"value\":[{\"@odata.type\":\"#Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI\",\"PaymentInstrumentID\":101902}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 1); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments(101902)/Microsoft.Test.OData.Services.ODataWCFService.CreditCardPI")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts?$expand=MyGiftCard +TEST(edit_link_singleton_contained_navigation) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(AccountID,MyGiftCard,MyGiftCard(GiftCardID))\", \ + \"value\":[{\"AccountID\":101,\"MyGiftCard@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyGiftCard(GiftCardID)/$entity\",\"MyGiftCard\":{\"GiftCardID\":301}}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 1); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)")); + + std::shared_ptr property_value; + auto ok = entity_value->get_property_value(U("MyGiftCard"), property_value); + auto contained_value = std::dynamic_pointer_cast(property_value); + VERIFY_ARE_EQUAL(contained_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyGiftCard")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyGiftCard +TEST(edit_link_singleton_contained_navigation_property) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyGiftCard(GiftCardID)/$entity\",\"GiftCardID\":301}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyGiftCard")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments?$expand=BillingStatements +TEST(edit_link_contained_in_contained_expand) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(103)/MyPaymentInstruments(PaymentInstrumentID,BillingStatements,BillingStatements(StatementID))/$entity\",\"PaymentInstrumentID\":103901,\"BillingStatements@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(103)/MyPaymentInstruments(103901)/BillingStatements(StatementID)\", \ + \"BillingStatements\":[{\"StatementID\":103901001},{\"StatementID\":103901002}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(103)/MyPaymentInstruments(103901)")); + + std::shared_ptr property_value; + auto ok = entity_value->get_property_value(U("BillingStatements"), property_value); + auto contained_values = std::dynamic_pointer_cast(property_value); + auto contained_value = std::dynamic_pointer_cast(contained_values->get_collection_values()[0]); + VERIFY_ARE_EQUAL(contained_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(103)/MyPaymentInstruments(103901)/BillingStatements(103901001)")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(103)/MyPaymentInstruments(103901)/BillingStatements +TEST(edit_link_contained_in_contained_property) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(103)/MyPaymentInstruments(103901)/BillingStatements/$entity\", \ + \"StatementID\":103901001,\"TransactionType\":\"OnlinePurchase\",\"TransactionDescription\":\"Digital goods: App\",\"Amount\":100.0}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(103)/MyPaymentInstruments(103901)/BillingStatements(103901001)")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments(101901)/TheStoredPI +TEST(edit_link_none_contained_in_contained_property) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#StoredPIs(StoredPIID)/$entity\",\"StoredPIID\":802}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/StoredPIs(802)")); +} + +//http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments?$expand=TheStoredPI +TEST(edit_link_none_contained_in_contained) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload + = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts(101)/MyPaymentInstruments(PaymentInstrumentID,TheStoredPI,TheStoredPI(StoredPIID))\", \ + \"value\":[{\"PaymentInstrumentID\":101901,\"TheStoredPI\":{\"StoredPIID\":802}}]}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_values = json_reader->deserilize(json_payload); + + auto collection_value = std::dynamic_pointer_cast(return_values); + VERIFY_IS_NOT_NULL(collection_value); + auto& entity_values = collection_value->get_collection_values(); + VERIFY_ARE_EQUAL(entity_values.size(), 1); + + auto entity_value = std::dynamic_pointer_cast(entity_values[0]); + VERIFY_ARE_EQUAL(entity_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)/MyPaymentInstruments(101901)")); + + std::shared_ptr property_value; + auto ok = entity_value->get_property_value(U("TheStoredPI"), property_value); + auto contained_value = std::dynamic_pointer_cast(property_value); + VERIFY_ARE_EQUAL(contained_value->get_edit_link().to_string(), U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService/StoredPIs(802)")); +} + +TEST(read_unicode_data_in_entity) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload_escaped = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts/$entity\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)\", \ + \"AccountID\":101,\"Country\":\"\u4e0a\u6d77\",\"AccountInfo\":{\"FirstName\":\"Alex\",\"LastName\":\"Green\"}}" + ); + + ::odata::utility::json::value json_payload_escaped = odata::utility::json::value::parse(_entity_payload_escaped); + auto return_payload_escaped = json_reader->deserilize(json_payload_escaped); + + auto entity_value = std::dynamic_pointer_cast(return_payload_escaped); + VERIFY_IS_NOT_NULL(entity_value); + ::odata::utility::string_t countryregion; + auto ok = entity_value->try_get(U("Country"), countryregion); + VERIFY_ARE_EQUAL(countryregion, U("上海")); + + ::odata::utility::string_t _entity_payload_unescaped = U( + "{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Accounts/$entity\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Accounts(101)\", \ + \"AccountID\":101,\"Country\":\"上海\",\"AccountInfo\":{\"FirstName\":\"Alex\",\"LastName\":\"Green\"}}" + ); + + ::odata::utility::json::value json_payload_unescaped = odata::utility::json::value::parse(_entity_payload_unescaped); + auto return_payload_unescaped = json_reader->deserilize(json_payload_unescaped); + + entity_value = std::dynamic_pointer_cast(return_payload_unescaped); + VERIFY_IS_NOT_NULL(entity_value); + ok = entity_value->try_get(U("Country"), countryregion); + VERIFY_ARE_EQUAL(countryregion, U("上海")); +} + +// +// to do add read all primitive type data test + +TEST(read_binary_data) +{ + auto json_reader = get_json_reader(); + VERIFY_IS_NOT_NULL(json_reader); + + ::odata::utility::string_t _entity_payload = U("{\"@odata.context\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/$metadata#Products/$entity\", \ + \"@odata.editLink\":\"http://odatae2etest.azurewebsites.net/cpptest/DefaultService/Products(5)\", \ + \"ProductID\":5,\"Picture\":\"UUJBQkFDWDEyKjEyMzQ1Njc4OTBhYmNkZQ==\"}"); + + ::odata::utility::json::value json_payload = odata::utility::json::value::parse(_entity_payload); + auto return_value = json_reader->deserilize(json_payload); + + auto entity_value = std::dynamic_pointer_cast(return_value); + + std::shared_ptr property_value; + entity_value->get_property_value(U("Picture"), property_value); + auto primitive_value = std::dynamic_pointer_cast(property_value); + auto binary_output = primitive_value->as>(); + VERIFY_ARE_EQUAL(binary_output[7], '1'); + VERIFY_ARE_EQUAL(binary_output[9], '*'); +} + +} + +}}} \ No newline at end of file diff --git a/tests/functional/core_test/odata_json_writer_test.cpp b/tests/functional/core_test/odata_json_writer_test.cpp new file mode 100644 index 0000000..aea9b5c --- /dev/null +++ b/tests/functional/core_test/odata_json_writer_test.cpp @@ -0,0 +1,443 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/core/odata_core.h" +#include "odata/core/odata_json_writer.h" + +using namespace ::odata::core; +using namespace ::odata::edm; + +namespace tests { namespace functional { namespace _odata { + +static std::shared_ptr get_json_writer() +{ + auto model = get_test_model(); + if (model) + { + return std::make_shared(model); + } + + return nullptr; +} + +typedef std::unordered_map<::odata::utility::string_t, ::odata::utility::string_t> map_type; +const map_type::value_type init_test_values[] = +{ + map_type::value_type(U("single_entity"), U("{\"AccountID\":100,\"AccountInfo\":{\"FirstName\":\"Leo\",\"LastName\":\"Hu\"},\"Country\":\"China\"}")), + + map_type::value_type(U("collection_of_entity"), U("[{\"AccountID\":100,\"Country\":\"China\"},{\"AccountID\":200,\"Country\":\"USA\"},{\"AccountID\":300,\"Country\":\"JP\"}]")), + + map_type::value_type(U("unicode_data_in_entity"), U("{\"AccountID\":100,\"Country\":\"上海\"}")), + map_type::value_type(U("single_complex"), U("{\"FirstName\":\"Leo\",\"LastName\":\"Hu\"}")), + map_type::value_type(U("collection_of_complex"), U("[{\"FirstName\":\"Leo\",\"LastName\":\"Hu\"},{\"FirstName\":\"Tian\",\"LastName\":\"Ouyang\"}]")), + map_type::value_type(U("single_primitive"), U("100")), + map_type::value_type(U("max_i16"), U("32767")), + map_type::value_type(U("min_i16"), U("-32768")), + map_type::value_type(U("max_i32"), U("2147483647")), + map_type::value_type(U("min_i32"), U("-2147483648")), + map_type::value_type(U("max_i64"), U("9223372036854775807")), + map_type::value_type(U("min_i64"), U("-9223372036854775808")), + map_type::value_type(U("primitive_bool_true"), U("true")), + map_type::value_type(U("primitive_bool_false"), U("false")), + map_type::value_type(U("primitive_sbyte"), U("234")), + map_type::value_type(U("primitive_byte"), U("123")), + map_type::value_type(U("primitive_datetime"), U("\"2013-12-31T00:00:00Z\"")), + map_type::value_type(U("primitive_duration"), U("\"PT38M38S\"")), + map_type::value_type(U("primitive_binary"), U("\"UUJBQkFDWDEyKlhjdmpr\"")), + map_type::value_type(U("collection_of_primitive"), U("[100,500,400,300,200]")), + map_type::value_type(U("single_enum"), U("\"Read\"")), + map_type::value_type(U("collection_of_enum"), U("[\"Read\",\"Write\",\"ReadWrite\"]")), +}; + +const static map_type test_data_map(init_test_values, init_test_values + 22); + +static bool check_writer_result(const ::odata::utility::string_t& check_key, const ::odata::utility::string_t& check_value) +{ + auto find_iter = test_data_map.find(check_key); + if (find_iter != test_data_map.end()) + { + return find_iter->second == check_value ? true : false; + } + + return false; +} + +SUITE(odata_json_writer_test_cases) +{ + +TEST(single_entity) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto complex_value = std::make_shared(model->find_complex_type(U("AccountInfo"))); + complex_value->set_value(U("FirstName"), U("Leo")); + complex_value->set_value(U("LastName"), U("Hu")); + + auto entity_value = std::make_shared(model->find_entity_type(U("Account"))); + entity_value->set_value(U("AccountID"), (int32_t)100); + entity_value->set_value(U("Country"), U("China")); + entity_value->set_value(U("AccountInfo"), complex_value); + + auto json_string = json_writer->serialize(entity_value).to_string(); + bool pass = check_writer_result(U("single_entity"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(collection_of_entity) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto entity_value_1 = std::make_shared(model->find_entity_type(U("Account"))); + entity_value_1->set_value(U("AccountID"), (int32_t)100); + entity_value_1->set_value(U("Country"), U("China")); + + auto entity_value_2 = std::make_shared(model->find_entity_type(U("Account"))); + entity_value_2->set_value(U("AccountID"), (int32_t)200); + entity_value_2->set_value(U("Country"), U("USA")); + + auto entity_value_3 = std::make_shared(model->find_entity_type(U("Account"))); + entity_value_3->set_value(U("AccountID"), (int32_t)300); + entity_value_3->set_value(U("Country"), U("JP")); + + auto collection_type = std::make_shared(model->find_entity_type(U("Account"))); + auto collection_value = std::make_shared(collection_type); + + collection_value->add_collection_value(entity_value_1); + collection_value->add_collection_value(entity_value_2); + collection_value->add_collection_value(entity_value_3); + + auto json_string = json_writer->serialize(collection_value).to_string(); + bool pass = check_writer_result(U("collection_of_entity"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_complex) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto complex_value = std::make_shared(model->find_complex_type(U("AccountInfo"))); + complex_value->set_value(U("FirstName"), U("Leo")); + complex_value->set_value(U("LastName"), U("Hu")); + + auto json_string = json_writer->serialize(complex_value).to_string(); + bool pass = check_writer_result(U("single_complex"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(collection_of_complex) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto complex_value_1 = std::make_shared(model->find_complex_type(U("AccountInfo"))); + complex_value_1->set_value(U("FirstName"), U("Leo")); + complex_value_1->set_value(U("LastName"), U("Hu")); + + auto complex_value_2 = std::make_shared(model->find_complex_type(U("AccountInfo"))); + complex_value_2->set_value(U("FirstName"), U("Tian")); + complex_value_2->set_value(U("LastName"), U("Ouyang")); + + auto collection_type = std::make_shared(model->find_complex_type(U("AccountInfo"))); + auto collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(complex_value_1); + collection_value->add_collection_value(complex_value_2); + + auto json_string = json_writer->serialize(collection_value).to_string(); + bool pass = check_writer_result(U("collection_of_complex"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto int32_value = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(100)); + + auto json_string = json_writer->serialize(int32_value).to_string(); + bool pass = check_writer_result(U("single_primitive"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_int16_min) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + int16_t value = -32768; + auto odata_value = std::make_shared(edm_primitive_type::INT16(), ::odata::utility::conversions::print_string(value)); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("min_i16"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_int16_max) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + int16_t value = 32767; + auto odata_value = std::make_shared(edm_primitive_type::INT16(), ::odata::utility::conversions::print_string(value)); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("max_i16"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_int32_max) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + int32_t value = 2147483647; + auto odata_value = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(value)); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("max_i32"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_int32_min) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + int32_t value = -2147483648; + auto odata_value = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(value)); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("min_i32"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_int64_max) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + int64_t value = 9223372036854775807; + auto odata_value = std::make_shared(edm_primitive_type::INT64(), ::odata::utility::conversions::print_string(value)); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("max_i64"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_int64_min) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + int64_t value = -9223372036854775808; + auto odata_value = std::make_shared(edm_primitive_type::INT64(), ::odata::utility::conversions::print_string(value)); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("min_i64"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +//TEST(single_primitive_float) +//{ +// auto json_writer = get_json_writer(); +// VERIFY_IS_NOT_NULL(json_writer); +// auto model = get_test_model(); +// +// float value = 123213.43; +// auto odata_value = odata_primitive_value::make_primitive_value(value); +// auto json_string = json_writer->serialize(odata_value).to_string(); +// bool pass = check_writer_result(U("primitive_float"), json_string); +// VERIFY_ARE_EQUAL(pass, true); +//} +// +//TEST(single_primitive_double) +//{ +// auto json_writer = get_json_writer(); +// VERIFY_IS_NOT_NULL(json_writer); +// auto model = get_test_model(); +// +//} + +TEST(single_primitive_bool_true) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + bool value = true; + auto odata_value = odata_primitive_value::make_primitive_value(value); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("primitive_bool_true"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_bool_false) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + bool value = false; + auto odata_value = odata_primitive_value::make_primitive_value(value); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("primitive_bool_false"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_binary) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + unsigned char ini_binary[] = {'Q', 'B', 'A', 'B', 'A', 'C', 'X', '1', '2', '*', 'X', 'c', 'v', 'j', 'k'}; + std::vector value(ini_binary, ini_binary + 15); + auto odata_value = odata_primitive_value::make_primitive_value(value); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("primitive_binary"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_sbyte) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + unsigned char value = 234; + auto odata_value = odata_primitive_value::make_primitive_value(value); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("primitive_sbyte"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_byte) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + char value = 123; + auto odata_value = odata_primitive_value::make_primitive_value(value); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("primitive_byte"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_duration) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto value = ::odata::utility::datetime::from_string(U("2013-12-31T00:00:00Z"), ::odata::utility::datetime::date_format::ISO_8601); + auto odata_value = odata_primitive_value::make_primitive_value(value); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("primitive_datetime"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_primitive_datetime) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + ::odata::utility::seconds value(2318); + auto odata_value = odata_primitive_value::make_primitive_value(value); + auto json_string = json_writer->serialize(odata_value).to_string(); + bool pass = check_writer_result(U("primitive_duration"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(collection_of_primitive) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto int32_value_1 = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(100)); + auto int32_value_2 = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(500)); + auto int32_value_3 = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(400)); + auto int32_value_4 = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(300)); + auto int32_value_5 = std::make_shared(edm_primitive_type::INT32(), ::odata::utility::conversions::print_string(200)); + + auto collection_type = std::make_shared(edm_primitive_type::INT32()); + auto collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(int32_value_1); + collection_value->add_collection_value(int32_value_2); + collection_value->add_collection_value(int32_value_3); + collection_value->add_collection_value(int32_value_4); + collection_value->add_collection_value(int32_value_5); + + auto json_string = json_writer->serialize(collection_value).to_string(); + bool pass = check_writer_result(U("collection_of_primitive"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(single_enum) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto enum_value = std::make_shared(model->find_enum_type(U("AccessLevel")), U("Read")); + + auto json_string = json_writer->serialize(enum_value).to_string(); + bool pass = check_writer_result(U("single_enum"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(collection_of_enum) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto enum_value_1 = std::make_shared(model->find_enum_type(U("AccessLevel")), U("Read")); + auto enum_value_2 = std::make_shared(model->find_enum_type(U("AccessLevel")), U("Write")); + auto enum_value_3 = std::make_shared(model->find_enum_type(U("AccessLevel")), U("ReadWrite")); + + auto collection_type = std::make_shared(model->find_enum_type(U("AccessLevel"))); + auto collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(enum_value_1); + collection_value->add_collection_value(enum_value_2); + collection_value->add_collection_value(enum_value_3); + + auto json_string = json_writer->serialize(collection_value).to_string(); + bool pass = check_writer_result(U("collection_of_enum"), json_string); + VERIFY_ARE_EQUAL(pass, true); +} + +TEST(unicode_data_in_entity) +{ + auto json_writer = get_json_writer(); + VERIFY_IS_NOT_NULL(json_writer); + auto model = get_test_model(); + + auto entity_value = std::make_shared(model->find_entity_type(U("Account"))); + entity_value->set_value(U("AccountID"), (int32_t)100); + entity_value->set_value(U("Country"), U("上海")); + + auto json_string = json_writer->serialize(entity_value).serialize(); + bool pass = check_writer_result(U("unicode_data_in_entity"), json_string); + + VERIFY_ARE_EQUAL(pass, true); +} + +} + +}}} \ No newline at end of file diff --git a/tests/functional/core_test/odata_uri_parser_test.cpp b/tests/functional/core_test/odata_uri_parser_test.cpp new file mode 100644 index 0000000..4c74767 --- /dev/null +++ b/tests/functional/core_test/odata_uri_parser_test.cpp @@ -0,0 +1,2384 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/core/odata_uri_parser.h" +#include "odata/core/odata_path_segment_visitor.h" +#include "odata/core/odata_query_node_visitor.h" + +using namespace ::odata::edm; +using namespace ::odata::core; + +namespace tests { namespace functional { namespace _odata { + +SUITE(odata_uri_parser_test_cases) +{ + +static std::shared_ptr get_model_from_csdl(const char *csdl) +{ + std::istringstream iss(std::move(std::string(csdl))); + auto model_reader = std::make_shared(iss); + + model_reader->parse(); + + return model_reader->get_model(); +} + +static std::shared_ptr get_test_model_with_person_with_one_key() +{ + static std::shared_ptr model; + + if (model == nullptr) + { + model = get_model_from_csdl( +"\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"); + } + + return model; +} + +static std::shared_ptr get_test_model_with_person_with_two_keys() +{ + static std::shared_ptr model; + + if (model == nullptr) + { + model = get_model_from_csdl( +"\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"); + } + + return model; +} + +static std::shared_ptr get_test_model_with_open_person() +{ + static std::shared_ptr model; + + if (model == nullptr) + { + model = get_model_from_csdl( +"\ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"); + } + + return model; +} + +// --BEGIN-- odata_path_parser + +TEST(invalid_relative_uri) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_path(U("People(ID='abc')")), odata_exception); +} + +TEST(empty_segment_identifier) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_path(U("(ID='abc')")), odata_exception); +} + +TEST(multiple_keys_without_name) +{ + odata_uri_parser parser(get_test_model_with_person_with_two_keys()); + + VERIFY_THROWS(parser.parse_path(U("/People('abc',123)")), odata_exception); +} + +TEST(duplicate_key_name) +{ + odata_uri_parser parser(get_test_model_with_person_with_two_keys()); + + VERIFY_THROWS(parser.parse_path(U("/People(ID1='abc',ID1=123)")), odata_exception); +} + +TEST(parenthesis_mismatch) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(ID='abc'")), odata_exception); +} + +TEST(single_quote_mismatch) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(ID='abc)")), odata_exception); +} + +TEST(unexpected_parenthesis_expression) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_path(U("/$metadata('123')")), odata_exception); + VERIFY_THROWS(parser.parse_path(U("/$batch('123')")), odata_exception); +} + +TEST(unexpected_path_root) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_path(U("/$count")), odata_exception); + VERIFY_THROWS(parser.parse_path(U("/$value")), odata_exception); + VERIFY_THROWS(parser.parse_path(U("/$ref")), odata_exception); +} + +TEST(unsupported_segment) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_path(U("/$all")), odata_exception); + VERIFY_THROWS(parser.parse_path(U("/$entity")), odata_exception); + VERIFY_THROWS(parser.parse_path(U("/$crossjoin")), odata_exception); +} + +TEST(resource_not_found) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People1(1)")), odata_exception); +} + +TEST(key_count_mismatch) +{ + odata_uri_parser parser(get_test_model_with_person_with_two_keys()); + + VERIFY_THROWS(parser.parse_path(U("/People(ID1=123)")), odata_exception); +} + +static void verify_entity_set_segment(std::shared_ptr segment, const ::odata::utility::string_t &name, const ::odata::utility::string_t &type) +{ + VERIFY_ARE_EQUAL(name, segment->as()->entity_set()->get_name()); + VERIFY_ARE_EQUAL(type, segment->as()->entity_type()->get_name()); +} + +static void verify_singleton_segment(std::shared_ptr segment, const ::odata::utility::string_t &name, const ::odata::utility::string_t &type) +{ + VERIFY_ARE_EQUAL(name, segment->as()->singleton()->get_name()); + VERIFY_ARE_EQUAL(type, segment->as()->entity_type()->get_name()); +} + +static void verify_key_segment(std::shared_ptr segment, const ::odata::utility::string_t &name, const ::odata::utility::string_t &value, ::size_t i = 0) +{ + VERIFY_ARE_EQUAL(name, segment->as()->key_at(i).first); + VERIFY_ARE_EQUAL(value, segment->as()->key_at(i).second->to_string()); +} + +TEST(bind_entity_set_1) +{ + odata_uri_parser parser(get_test_model_with_person_with_two_keys()); + auto path = parser.parse_path(U("/People(ID1=123,ID2='abc')")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(2, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID1"), U("123")); + verify_key_segment(path->segment_at(1), U("ID2"), U("abc"), 1); +} + +TEST(bind_entity_set_2) +{ + odata_uri_parser parser(get_test_model_with_person_with_two_keys()); + auto path = parser.parse_path(U("/People(ID2='abc',ID1=123)")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(2, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID2"), U("abc"), 0); + verify_key_segment(path->segment_at(1), U("ID1"), U("123"), 1); +} + +TEST(comma_in_key_name) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(ID='ab,c')")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("ab,c")); +} + +TEST(bind_singleton) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/CEO")); + + VERIFY_ARE_EQUAL(1, path->segments().size()); + + verify_singleton_segment(path->segment_at(0), U("CEO"), U("Person")); +} + +TEST(unexpected_parenthesis_expression_after_singleton) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/CEO(ID=12)")), odata_exception); +} + +TEST(key_name_mismatch_1) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(ID1=123)")), odata_exception); +} + +TEST(key_name_mismatch_2) +{ + odata_uri_parser parser(get_test_model_with_person_with_two_keys()); + + VERIFY_THROWS(parser.parse_path(U("/People(ID1=123,ID3='abc')")), odata_exception); +} + +TEST(omit_name_when_only_one_key) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(123)")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("123")); +} + +static void verify_structural_property_segment(std::shared_ptr segment, const ::odata::utility::string_t &owning_type, const ::odata::utility::string_t &property) +{ + VERIFY_ARE_EQUAL(owning_type, segment->as()->owning_type()->get_name()); + VERIFY_ARE_EQUAL(property, segment->as()->property()->get_name()); +} + +TEST(bind_entity_set_structural_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(123)/ID")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("123")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("ID")); +} + +TEST(bind_singleton_structural_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/CEO/ID")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + verify_singleton_segment(path->segment_at(0), U("CEO"), U("Person")); + + verify_structural_property_segment(path->segment_at(1), U("Person"), U("ID")); +} + +TEST(unexpected_parenthesis_expression_after_value) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/CEO/ID/$value(1)")), odata_exception); +} + +TEST(bind_value_after_structural_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(123)/ID/$value")); + + VERIFY_ARE_EQUAL(4, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("123")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("ID")); + + VERIFY_ARE_EQUAL(::odata::core::odata_path_segment_type::Value, path->segment_at(3)->segment_type()); +} + +TEST(bind_type_after_entity_set) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(123)/MyNS.VIP")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("123")); + + VERIFY_ARE_EQUAL(U("VIP"), path->segment_at(2)->as()->type()->get_name()); +} + +TEST(bind_type_after_singleton) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/CEO/MyNS.VIP")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + verify_singleton_segment(path->segment_at(0), U("CEO"), U("Person")); + + VERIFY_ARE_EQUAL(U("VIP"), path->segment_at(1)->as()->type()->get_name()); +} + +TEST(not_open_entity_type) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/NotDeclared")), odata_exception); +} + +TEST(not_open_complex_type) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/Address/NotDeclared")), odata_exception); +} + +TEST(bind_dynamic_property_on_open_entity_type) +{ + odata_uri_parser parser(get_test_model_with_open_person()); + auto path = parser.parse_path(U("/People(1)/NotDeclared")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + VERIFY_ARE_EQUAL(U("NotDeclared"), path->segment_at(2)->as()->property_name()); +} + +TEST(bind_dynamic_property_on_open_complex_type) +{ + odata_uri_parser parser(get_test_model_with_open_person()); + auto path = parser.parse_path(U("/People(1)/Address/NotDeclared")); + + VERIFY_ARE_EQUAL(4, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("Address")); + + VERIFY_ARE_EQUAL(U("NotDeclared"), path->segment_at(3)->as()->property_name()); +} + +TEST(bind_multilevel_dynamic_property_on_open_type) +{ + odata_uri_parser parser(get_test_model_with_open_person()); + auto path = parser.parse_path(U("/People(1)/Address/NotDeclared1/NotDeclared2/NotDeclared3")); + + VERIFY_ARE_EQUAL(6, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("Address")); + + VERIFY_ARE_EQUAL(U("NotDeclared1"), path->segment_at(3)->as()->property_name()); + + VERIFY_ARE_EQUAL(U("NotDeclared2"), path->segment_at(4)->as()->property_name()); + + VERIFY_ARE_EQUAL(U("NotDeclared3"), path->segment_at(5)->as()->property_name()); +} + +TEST(unexpected_parenthesis_expression_after_structural_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/ID(2)")), odata_exception); +} + +static void verify_navigation_property_segment( + std::shared_ptr segment, + const ::odata::utility::string_t &name, + const ::odata::utility::string_t &type, + const ::odata::utility::string_t &target) +{ + VERIFY_ARE_EQUAL(name, segment->as()->property()->get_name()); + VERIFY_ARE_EQUAL(type, segment->as()->navigation_type()->get_navigation_type()->get_name()); + VERIFY_ARE_EQUAL(target, segment->as()->navigation_type()->get_binded_navigation_source()->get_name()); +} + +static void verify_navigation_property_segment( + std::shared_ptr segment, + const ::odata::utility::string_t &name, + const ::odata::utility::string_t &type) +{ + VERIFY_ARE_EQUAL(name, segment->as()->property()->get_name()); + VERIFY_ARE_EQUAL(type, segment->as()->navigation_type()->get_navigation_type()->get_name()); + VERIFY_IS_NULL(segment->as()->navigation_type()->get_binded_navigation_source()); +} + +TEST(bind_single_value_navigation_property_without_key) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/Parent")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_navigation_property_segment(path->segment_at(2), U("Parent"), U("Person"), U("People")); +} + +TEST(bind_navigation_property_without_key) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/Mates")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_navigation_property_segment(path->segment_at(2), U("Mates"), U("Collection(MyNS.Person)"), U("People")); +} + +TEST(unexpected_parenthesis_expression_after_single_value_navigation_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/Parent(2)")), odata_exception); +} + +TEST(bind_navigation_property_with_key) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/Mates(3)")); + + VERIFY_ARE_EQUAL(4, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_navigation_property_segment(path->segment_at(2), U("Mates"), U("Collection(MyNS.Person)"), U("People")); + + VERIFY_ARE_EQUAL(1, path->segment_at(3)->as()->keys().size()); + verify_key_segment(path->segment_at(3), U("ID"), U("3")); +} + +TEST(no_navigation_source) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/Friends(3)")), odata_exception); +} + +TEST(bind_contained_navigation_property_without_key) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/Brothers")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_navigation_property_segment(path->segment_at(2), U("Brothers"), U("Collection(MyNS.Person)")); +} + +TEST(bind_contained_navigation_property_with_key) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/Brothers(3)")); + + VERIFY_ARE_EQUAL(4, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_navigation_property_segment(path->segment_at(2), U("Brothers"), U("Collection(MyNS.Person)")); + + VERIFY_ARE_EQUAL(1, path->segment_at(3)->as()->keys().size()); + verify_key_segment(path->segment_at(3), U("ID"), U("3")); +} + +static void verify_operation_import_segment_parameter(std::shared_ptr segment, const ::odata::utility::string_t &name, const ::odata::utility::string_t &value, ::size_t i = 0) +{ + VERIFY_ARE_EQUAL(name, segment->as()->parameter_at(i).first); + VERIFY_ARE_EQUAL(value, segment->as()->parameter_at(i).second->to_string()); +} + +TEST(bind_operation_import_1) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/AddIntAndDouble(a=1,b=2)")); + + VERIFY_ARE_EQUAL(1, path->segments().size()); + + VERIFY_ARE_EQUAL(U("AddIntAndDouble"), path->segment_at(0)->as()->operation_import()->get_name()); + VERIFY_ARE_EQUAL(2, path->segment_at(0)->as()->parameters().size()); + verify_operation_import_segment_parameter(path->segment_at(0), U("a"), U("1"), 0); + verify_operation_import_segment_parameter(path->segment_at(0), U("b"), U("2"), 1); +} + +TEST(bind_operation_import_2) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/AddIntAndDouble(b=1,a=2)")); + + VERIFY_ARE_EQUAL(1, path->segments().size()); + + VERIFY_ARE_EQUAL(U("AddIntAndDouble"), path->segment_at(0)->as()->operation_import()->get_name()); + VERIFY_ARE_EQUAL(2, path->segment_at(0)->as()->parameters().size()); + verify_operation_import_segment_parameter(path->segment_at(0), U("b"), U("1"), 0); + verify_operation_import_segment_parameter(path->segment_at(0), U("a"), U("2"), 1); +} + +TEST(parameter_count_mismatch_1) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble(a=2)")), odata_exception); +} + +TEST(parameter_count_mismatch_2) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble(a=2,b=3,c=4)")), odata_exception); +} + +TEST(duplicate_parameter) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble(a=2,a=1)")), odata_exception); +} + +TEST(parameter_name_mismatch) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble(a=2,c=1)")), odata_exception); +} + +TEST(missing_parameter_value) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble(a=2,c=)")), odata_exception); +} + +TEST(missing_parameter_name) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble(a=2,=1)")), odata_exception); +} + +TEST(missing_parenthesis_in_parameter) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble(a=2,b=1")), odata_exception); +} + +TEST(bind_bound_operation_import) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble1(a=2,b=1)")), odata_exception); +} + +TEST(bind_operation_to_entity) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/MyNS.GetFavNum")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + VERIFY_ARE_EQUAL(U("GetFavNum"), path->segment_at(2)->as()->operation()->get_name()); + VERIFY_IS_TRUE(path->segment_at(2)->as()->parameters().empty()); +} + +TEST(parameter_mismatch_for_bound_operation) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/MyNS.GetFavNum(a=1)")), odata_exception); +} + +TEST(bind_operation_to_primitive) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/ID/MyNS.AddOne")); + + VERIFY_ARE_EQUAL(4, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("ID")); + + VERIFY_ARE_EQUAL(U("AddOne"), path->segment_at(3)->as()->operation()->get_name()); + VERIFY_IS_TRUE(path->segment_at(3)->as()->parameters().empty()); +} + +static void verify_operation_segment_parameter(std::shared_ptr segment, const ::odata::utility::string_t &name, const ::odata::utility::string_t &value, ::size_t i = 0) +{ + VERIFY_ARE_EQUAL(name, segment->as()->parameter_at(i).first); + VERIFY_ARE_EQUAL(value, segment->as()->parameter_at(i).second->to_string()); +} + +TEST(bind_operation_to_primitive_1) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/ID/MyNS.AddWith(y=1)")); + + VERIFY_ARE_EQUAL(4, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("ID")); + + VERIFY_ARE_EQUAL(U("AddWith"), path->segment_at(3)->as()->operation()->get_name()); + VERIFY_ARE_EQUAL(1, path->segment_at(3)->as()->parameters().size()); + verify_operation_segment_parameter(path->segment_at(3), U("y"), U("1")); +} + +TEST(parameter_mismatch_for_bound_operation_1) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/ID/MyNS.AddWith()")), odata_exception); +} + +TEST(parameter_mismatch_for_bound_operation_2) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/ID/MyNS.AddWith(y=1,z=2)")), odata_exception); +} + +TEST(bind_operation_to_complex) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/Address/MyNS.GetCountry")); + + VERIFY_ARE_EQUAL(4, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("Address")); + + VERIFY_ARE_EQUAL(U("GetCountry"), path->segment_at(3)->as()->operation()->get_name()); + VERIFY_IS_TRUE(path->segment_at(3)->as()->parameters().empty()); +} + +TEST(binding_type_mismatch) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/ID/MyNS.GetFavNum")), odata_exception); +} + +TEST(compose_bound_operations) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/ID/MyNS.AddOne/MyNS.AddOne")); + + VERIFY_ARE_EQUAL(5, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + verify_structural_property_segment(path->segment_at(2), U("Person"), U("ID")); + + VERIFY_ARE_EQUAL(U("AddOne"), path->segment_at(3)->as()->operation()->get_name()); + VERIFY_IS_TRUE(path->segment_at(3)->as()->parameters().empty()); + + VERIFY_ARE_EQUAL(U("AddOne"), path->segment_at(4)->as()->operation()->get_name()); + VERIFY_IS_TRUE(path->segment_at(4)->as()->parameters().empty()); +} + +TEST(compose_bound_operation_with_unbound_one) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/AddIntAndDouble(a=1,b=2)/MyNS.AddOne")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + VERIFY_ARE_EQUAL(U("AddIntAndDouble"), path->segment_at(0)->as()->operation_import()->get_name()); + VERIFY_ARE_EQUAL(2, path->segment_at(0)->as()->parameters().size()); + verify_operation_import_segment_parameter(path->segment_at(0), U("a"), U("1"), 0); + verify_operation_import_segment_parameter(path->segment_at(0), U("b"), U("2"), 1); + + VERIFY_ARE_EQUAL(U("AddOne"), path->segment_at(1)->as()->operation()->get_name()); + VERIFY_IS_TRUE(path->segment_at(1)->as()->parameters().empty()); +} + +TEST(compose_noncomposable_bound_operation) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/ID/MyNS.AddTwo/MyNS.AddOne")), odata_exception); +} + +TEST(compose_noncomposable_unbound_operation_import) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/AddIntAndDouble2(a=1,b=2)/MyNS.AddOne")), odata_exception); +} + +TEST(segment_follow_metadata) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_path(U("/$metadata/MyNS.AddOne")), odata_exception); +} + +TEST(segment_follow_value) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/ID/$value/MyNS.AddOne")), odata_exception); +} + +TEST(segment_follow_batch) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_path(U("/$batch/MyNS.AddOne")), odata_exception); +} + +TEST(segment_follow_count) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People/$count/MyNS.AddOne")), odata_exception); +} + +TEST(segment_follow_ref) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + + VERIFY_THROWS(parser.parse_path(U("/People(1)/$ref/MyNS.AddOne")), odata_exception); +} + +TEST(count_after_entity_set) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People/$count")); + + VERIFY_ARE_EQUAL(2, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(::odata::core::odata_path_segment_type::Count, path->segment_at(1)->as()->segment_type()); +} + +TEST(ref_after_entity) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto path = parser.parse_path(U("/People(1)/$ref")); + + VERIFY_ARE_EQUAL(3, path->segments().size()); + + verify_entity_set_segment(path->segment_at(0), U("People"), U("Person")); + + VERIFY_ARE_EQUAL(1, path->segment_at(1)->as()->keys().size()); + verify_key_segment(path->segment_at(1), U("ID"), U("1")); + + VERIFY_ARE_EQUAL(::odata::core::odata_path_segment_type::Ref, path->segment_at(2)->as()->segment_type()); +} + +TEST(batch_on_path_root) +{ + odata_uri_parser parser(std::make_shared()); + auto path = parser.parse_path(U("/$batch")); + + VERIFY_ARE_EQUAL(1, path->segments().size()); + + VERIFY_ARE_EQUAL(::odata::core::odata_path_segment_type::Batch, path->segment_at(0)->as()->segment_type()); +} + +TEST(metadata_on_path_root) +{ + odata_uri_parser parser(std::make_shared()); + auto path = parser.parse_path(U("/$metadata")); + + VERIFY_ARE_EQUAL(1, path->segments().size()); + + VERIFY_ARE_EQUAL(::odata::core::odata_path_segment_type::Metadata, path->segment_at(0)->as()->segment_type()); +} + +// --END-- odata_path_parser + +// --BEGIN-- odata_expression_lexer + +TEST(scan_guid_literal) +{ + auto token = odata_expression_lexer::create_lexer(U("01234567-89ab-cdef-0123-456789abcdef"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::GuidLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("01234567-89ab-cdef-0123-456789abcdef"), token->text()); +} + +TEST(scan_guid_literal_start_with_letter) +{ + auto token = odata_expression_lexer::create_lexer(U("a1234567-89ab-cdef-0123-456789abcdef"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::GuidLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("a1234567-89ab-cdef-0123-456789abcdef"), token->text()); +} + +TEST(scan_guid_literal_negative) +{ + auto token = odata_expression_lexer::create_lexer(U("-01234567-89ab-cdef-0123-456789abcdef"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::GuidLiteral, token->token_kind()); +} + +TEST(scan_guid_literal_wrong_char) +{ + auto token = odata_expression_lexer::create_lexer(U("01234g67-89ab-cdef-0123-456789abcdef"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::GuidLiteral, token->token_kind()); +} + +TEST(scan_guid_literal_wrong_length) +{ + auto token = odata_expression_lexer::create_lexer(U("01234567-89ab-cdef-456789abcdef"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::GuidLiteral, token->token_kind()); +} + +TEST(scan_datetimeoffset_no_seconds) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-09-03T13:52Z"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("2012-09-03T13:52Z"), token->text()); +} + +TEST(scan_datetimeoffset_seconds) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-09-03T08:09:02Z"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("2012-09-03T08:09:02Z"), token->text()); +} + +TEST(scan_datetimeoffset_frac_seconds) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-08-31T18:19:22.1Z"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("2012-08-31T18:19:22.1Z"), token->text()); +} + +TEST(scan_datetimeoffset_year_zero) +{ + auto token = odata_expression_lexer::create_lexer(U("0000-01-01T00:00Z"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("0000-01-01T00:00Z"), token->text()); +} + +TEST(scan_datetimeoffset_negative) +{ + auto token = odata_expression_lexer::create_lexer(U("-10000-04-01T00:00Z"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("-10000-04-01T00:00Z"), token->text()); +} + +TEST(scan_datetimeoffset_tz2) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-09-03T14:53+02:00"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("2012-09-03T14:53+02:00"), token->text()); +} + +TEST(scan_datetimeoffset_tzn2) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-09-03T14:53-02:00"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("2012-09-03T14:53-02:00"), token->text()); +} + +TEST(scan_datetimeoffset_wrong_year) +{ + auto token = odata_expression_lexer::create_lexer(U("201-09-03T14:53-02:00"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); +} + +TEST(scan_datetimeoffset_wrong_month) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-091-03T14:53-02:00"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); +} + +TEST(scan_datetimeoffset_wrong_hour) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-09-03T1:53-02:00"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); +} + +TEST(scan_datetimeoffset_wrong_format) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-09-03T14::53-02:00"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); +} + +TEST(scan_datetimeoffset_wrong_tz) +{ + auto token = odata_expression_lexer::create_lexer(U("2012-09-03T14:53-2:00"))->current_token(); + + VERIFY_ARE_NOT_EQUAL(odata_expression_token_kind::DateTimeOffsetLiteral, token->token_kind()); +} + +TEST(scan_int64_max) +{ + auto token = odata_expression_lexer::create_lexer(U("9223372036854775807"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::Int64Literal, token->token_kind()); + VERIFY_ARE_EQUAL(U("9223372036854775807"), token->text()); +} + +TEST(scan_int64_min) +{ + auto token = odata_expression_lexer::create_lexer(U("-9223372036854775808"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::Int64Literal, token->token_kind()); + VERIFY_ARE_EQUAL(U("-9223372036854775808"), token->text()); +} + +TEST(scan_int32_max) +{ + auto token = odata_expression_lexer::create_lexer(U("2147483647"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::Int32Literal, token->token_kind()); + VERIFY_ARE_EQUAL(U("2147483647"), token->text()); +} + +TEST(scan_int32_min) +{ + auto token = odata_expression_lexer::create_lexer(U("-2147483648"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::Int32Literal, token->token_kind()); + VERIFY_ARE_EQUAL(U("-2147483648"), token->text()); +} + +TEST(scan_int32) +{ + auto token = odata_expression_lexer::create_lexer(U("10"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::Int32Literal, token->token_kind()); + VERIFY_ARE_EQUAL(U("10"), token->text()); +} + +TEST(scan_decimal) +{ + auto token = odata_expression_lexer::create_lexer(U("1.175"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DecimalLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("1.175"), token->text()); +} + +TEST(scan_single_min) +{ + auto token = odata_expression_lexer::create_lexer(U("1.175494351e-38"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::SingleLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("1.175494351e-38"), token->text()); +} + +TEST(scan_single_max) +{ + auto token = odata_expression_lexer::create_lexer(U("3.402823466e+38"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::SingleLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("3.402823466e+38"), token->text()); +} + +TEST(scan_double_min) +{ + auto token = odata_expression_lexer::create_lexer(U("2.2250738585072014e-308"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DoubleLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("2.2250738585072014e-308"), token->text()); +} + +TEST(scan_double_max) +{ + auto token = odata_expression_lexer::create_lexer(U("1.7976931348623158e+308"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DoubleLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("1.7976931348623158e+308"), token->text()); +} + +TEST(scan_double_inf) +{ + auto token = odata_expression_lexer::create_lexer(U("INF"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DoubleLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("INF"), token->text()); +} + +TEST(scan_double_negative_inf) +{ + auto token = odata_expression_lexer::create_lexer(U("-INF"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DoubleLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("-INF"), token->text()); +} + +TEST(scan_double_nan) +{ + auto token = odata_expression_lexer::create_lexer(U("NaN"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DoubleLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("NaN"), token->text()); +} + +TEST(scan_true) +{ + auto token = odata_expression_lexer::create_lexer(U("true"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::BooleanLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("true"), token->text()); +} + +TEST(scan_false) +{ + auto token = odata_expression_lexer::create_lexer(U("false"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::BooleanLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("false"), token->text()); +} + +TEST(scan_null) +{ + auto token = odata_expression_lexer::create_lexer(U("null"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::NullLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("null"), token->text()); +} + +TEST(scan_duration_literal) +{ + auto token = odata_expression_lexer::create_lexer(U("duration'P6DT23H59M59.9999S'"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::DurationLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("duration'P6DT23H59M59.9999S'"), token->text()); +} + +TEST(scan_binary_literal) +{ + auto token = odata_expression_lexer::create_lexer(U("binary'Zm9vYmE='"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::BinaryLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("binary'Zm9vYmE='"), token->text()); +} + +TEST(scan_quoted_literal) +{ + auto token = odata_expression_lexer::create_lexer(U("geography'SRID=0;LineString(142.1 64.1,3.14 2.78)'"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::QuotedLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("geography'SRID=0;LineString(142.1 64.1,3.14 2.78)'"), token->text()); +} + +TEST(scan_string_literal) +{ + auto token = odata_expression_lexer::create_lexer(U("'abc'"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::StringLiteral, token->token_kind()); + VERIFY_ARE_EQUAL(U("'abc'"), token->text()); + VERIFY_ARE_EQUAL(U("abc"), token->to_primitive_value()->to_string()); +} + +TEST(scan_parameter_alias) +{ + auto token = odata_expression_lexer::create_lexer(U("@word"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::ParameterAlias, token->token_kind()); + VERIFY_ARE_EQUAL(U("@word"), token->text()); +} + +TEST(scan_json_object) +{ + auto token = odata_expression_lexer::create_lexer(U("{\"Name\":\"Value\"}"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::JsonObjectOrArray, token->token_kind()); + VERIFY_ARE_EQUAL(U("{\"Name\":\"Value\"}"), token->text()); +} + +TEST(scan_json_array) +{ + auto token = odata_expression_lexer::create_lexer(U("[1, 2,3]"))->current_token(); + + VERIFY_ARE_EQUAL(odata_expression_token_kind::JsonObjectOrArray, token->token_kind()); + VERIFY_ARE_EQUAL(U("[1, 2,3]"), token->text()); +} + +// --END-- odata_expression_lexer + +// --BEGIN-- odata_expression_parser + +static void verify_is_operator_or_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::BinaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(binary_operator_kind::Or, node->as()->operator_kind()); +} + +static void verify_is_operator_and_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::BinaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(binary_operator_kind::And, node->as()->operator_kind()); +} + +static void verify_is_true(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::Constant, node->node_kind()); + VERIFY_ARE_EQUAL(::odata::edm::edm_primitive_type::BOOLEAN(), node->as()->value()->get_value_type()); + VERIFY_ARE_EQUAL(U("true"), node->as()->value()->to_string()); +} + +static void verify_is_false(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::Constant, node->node_kind()); + VERIFY_ARE_EQUAL(::odata::edm::edm_primitive_type::BOOLEAN(), node->as()->value()->get_value_type()); + VERIFY_ARE_EQUAL(U("false"), node->as()->value()->to_string()); +} + +TEST(parse_logical_or) +{ + auto node = odata_expression_parser::parse_expression(U("true or false or true")); + + verify_is_operator_or_node(node); + + verify_is_true(node->as()->right()); + + auto node1 = node->as()->left(); + verify_is_operator_or_node(node1); + verify_is_true(node1->as()->left()); + verify_is_false(node1->as()->right()); +} + +TEST(parse_logical_and) +{ + auto node = odata_expression_parser::parse_expression(U("true and false and true")); + + verify_is_operator_and_node(node); + + verify_is_true(node->as()->right()); + + auto node1 = node->as()->left(); + verify_is_operator_and_node(node1); + verify_is_true(node1->as()->left()); + verify_is_false(node1->as()->right()); +} + +TEST(parse_logical_and_or) +{ + auto node = odata_expression_parser::parse_expression(U("true or false and true")); + + verify_is_operator_or_node(node); + + verify_is_true(node->as()->left()); + + auto node1 = node->as()->right(); + verify_is_operator_and_node(node1); + verify_is_false(node1->as()->left()); + verify_is_true(node1->as()->right()); +} + +static void verify_is_operator_not_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::UnaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(unary_operator_kind::Not, node->as()->operator_kind()); +} + +TEST(parse_logical_not) +{ + auto node = odata_expression_parser::parse_expression(U("true or not false")); + + verify_is_operator_or_node(node); + + verify_is_true(node->as()->left()); + + auto node1 = node->as()->right(); + verify_is_operator_not_node(node1); + verify_is_false(node1->as()->operand()); +} + +static void verify_is_operator_lt_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::BinaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(binary_operator_kind::LessThan, node->as()->operator_kind()); +} + +static void verify_is_int(std::shared_ptr node, const ::odata::utility::string_t &str) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::Constant, node->node_kind()); + VERIFY_ARE_EQUAL(::odata::edm::edm_primitive_type::INT32(), node->as()->value()->get_value_type()); + VERIFY_ARE_EQUAL(str, node->as()->value()->to_string()); +} + +TEST(parse_comparsion) +{ + auto node = odata_expression_parser::parse_expression(U("1 lt 2")); + + verify_is_operator_lt_node(node); + + verify_is_int(node->as()->left(), U("1")); + verify_is_int(node->as()->right(), U("2")); +} + +static void verify_is_operator_add_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::BinaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(binary_operator_kind::Add, node->as()->operator_kind()); +} + +static void verify_is_operator_mul_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::BinaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(binary_operator_kind::Multiply, node->as()->operator_kind()); +} + +static void verify_is_operator_neg_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::UnaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(unary_operator_kind::Negate, node->as()->operator_kind()); +} + +TEST(parse_add_mul_neg) +{ + auto node = odata_expression_parser::parse_expression(U("1 mul 2 add 2 mul - 3")); + + verify_is_operator_add_node(node); + + auto node1 = node->as()->left(); + verify_is_operator_mul_node(node1); + verify_is_int(node1->as()->left(), U("1")); + verify_is_int(node1->as()->right(), U("2")); + + auto node2 = node->as()->right(); + verify_is_operator_mul_node(node2); + verify_is_int(node2->as()->left(), U("2")); + + auto node3 = node2->as()->right(); + verify_is_operator_neg_node(node3); + verify_is_int(node3->as()->operand(), U("3")); +} + +TEST(parse_parenthesis) +{ + auto node = odata_expression_parser::parse_expression(U("(1 add 2) mul (2 add 3)")); + + verify_is_operator_mul_node(node); + + auto node1 = node->as()->left(); + verify_is_operator_add_node(node1); + verify_is_int(node1->as()->left(), U("1")); + verify_is_int(node1->as()->right(), U("2")); + + auto node2 = node->as()->right(); + verify_is_operator_add_node(node2); + verify_is_int(node2->as()->left(), U("2")); + verify_is_int(node2->as()->right(), U("3")); +} + +static void verify_is_property_access_node(std::shared_ptr node, const ::odata::utility::string_t &text) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::PropertyAccess, node->node_kind()); + VERIFY_ARE_EQUAL(text, node->as()->property_name()); +} + +static void verify_is_type_cast_node(std::shared_ptr node, const ::odata::utility::string_t &text) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::TypeCast, node->node_kind()); + VERIFY_ARE_EQUAL(text, node->as()->type_name()); +} + +TEST(parse_segment) +{ + auto node = odata_expression_parser::parse_expression(U("Items")); + + verify_is_property_access_node(node, U("Items")); +} + +TEST(parse_segments) +{ + auto node = odata_expression_parser::parse_expression(U("Item/Quantity")); + + verify_is_property_access_node(node, U("Quantity")); + VERIFY_ARE_EQUAL(U("Item"), node->as()->parent()->as()->property_name()); +} + +TEST(parse_type_cast) +{ + auto node = odata_expression_parser::parse_expression(U("Item/MyNS.ConcreteItem")); + + verify_is_type_cast_node(node, U("MyNS.ConcreteItem")); + VERIFY_ARE_EQUAL(U("Item"), node->as()->parent()->as()->type_name()); +} + +static void verify_is_operator_gt_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::BinaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(binary_operator_kind::GreaterThan, node->as()->operator_kind()); +} + +static void verify_is_range_variable_node(std::shared_ptr node, const ::odata::utility::string_t &name) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::RangeVariable, node->node_kind()); + VERIFY_ARE_EQUAL(name, node->as()->name()); +} + +TEST(parse_lambda) +{ + auto node = odata_expression_parser::parse_expression(U("Items/any(d:d/Quantity gt 100)")); + + VERIFY_ARE_EQUAL(odata_query_node_kind::Lambda, node->node_kind()); + + auto node1 = node->as(); + VERIFY_IS_TRUE(node1->is_any()); + VERIFY_ARE_EQUAL(U("d"), node1->parameter()); + + auto node2 = node1->expression(); + verify_is_operator_gt_node(node2); + + auto node3 = node2->as()->left(); + verify_is_property_access_node(node3, U("Quantity")); + verify_is_range_variable_node(node3->as()->parent(), U("d")); + + auto node4 = node2->as()->right(); + verify_is_int(node4, U("100")); +} + +TEST(duplicate_lambda_parameter) +{ + VERIFY_THROWS(odata_expression_parser::parse_expression(U("Items/any(d:d/Quantity gt 100 and Items/any(d:d/Quantity gt 100))")), odata_exception); +} + +TEST(parse_multiple_lambda) +{ + auto node = odata_expression_parser::parse_expression(U("Items/any(d:d/Quantity gt 100) and Items/all(d:d/Quantity lt 100)")); + + verify_is_operator_and_node(node); + + auto node_left = node->as()->left(); + + VERIFY_ARE_EQUAL(odata_query_node_kind::Lambda, node_left->node_kind()); + + auto node1 = node_left->as(); + VERIFY_IS_TRUE(node1->is_any()); + VERIFY_ARE_EQUAL(U("d"), node1->parameter()); + + auto node2 = node1->expression(); + verify_is_operator_gt_node(node2); + + auto node3 = node2->as()->left(); + verify_is_property_access_node(node3, U("Quantity")); + verify_is_range_variable_node(node3->as()->parent(), U("d")); + + auto node4 = node2->as()->right(); + verify_is_int(node4, U("100")); + + auto node_right = node->as()->right(); + + VERIFY_ARE_EQUAL(odata_query_node_kind::Lambda, node_right->node_kind()); + + auto node5 = node_right->as(); + VERIFY_IS_FALSE(node5->is_any()); + VERIFY_ARE_EQUAL(U("d"), node1->parameter()); + + auto node6 = node5->expression(); + verify_is_operator_lt_node(node6); + + auto node7 = node6->as()->left(); + verify_is_property_access_node(node7, U("Quantity")); + verify_is_range_variable_node(node7->as()->parent(), U("d")); + + auto node8 = node6->as()->right(); + verify_is_int(node8, U("100")); +} + +static void verify_is_function_call_node(std::shared_ptr node, const ::odata::utility::string_t &name) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::FunctionCall, node->node_kind()); + VERIFY_ARE_EQUAL(name, node->as()->name()); +} + +static void verify_function_call_parameter( + std::shared_ptr node, + ::size_t i, + const ::odata::utility::string_t &name, + int value_kind) +{ + VERIFY_ARE_EQUAL(name, node->as()->parameter_at(i).first); + VERIFY_ARE_EQUAL(value_kind, node->as()->parameter_at(i).second->node_kind()); +} + +static void verify_is_string(std::shared_ptr node, const ::odata::utility::string_t &str) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::Constant, node->node_kind()); + VERIFY_ARE_EQUAL(::odata::edm::edm_primitive_type::STRING(), node->as()->value()->get_value_type()); + VERIFY_ARE_EQUAL(str, node->as()->value()->to_string()); +} + +TEST(parse_canonical_function) +{ + auto node = odata_expression_parser::parse_expression(U("contains(CompanyName,'Alfreds')")); + verify_is_function_call_node(node, U("contains")); + verify_function_call_parameter(node, 0, U(""), odata_query_node_kind::PropertyAccess); + verify_function_call_parameter(node, 1, U(""), odata_query_node_kind::Constant); + verify_is_string(node->as()->parameter_at(1).second, U("Alfreds")); +} + +TEST(parse_bound_operation) +{ + auto node = odata_expression_parser::parse_expression(U("MyNS.GetFavouriteColors(ID=1,Name='Foo')")); + verify_is_function_call_node(node, U("MyNS.GetFavouriteColors")); + verify_function_call_parameter(node, 0, U("ID"), odata_query_node_kind::Constant); + verify_is_int(node->as()->parameter_at(0).second, U("1")); + verify_function_call_parameter(node, 1, U("Name"), odata_query_node_kind::Constant); + verify_is_string(node->as()->parameter_at(1).second, U("Foo")); +} + +// --END-- odata_expression_parser + +// --BEGIN-- odata_query_option_parser + +TEST(parse_top_empty) +{ + odata_uri_parser parser(std::make_shared()); + auto top = parser.parse_top(::odata::utility::string_t()); + + VERIFY_IS_TRUE(null_value == top); +} + +TEST(parse_top_value) +{ + odata_uri_parser parser(std::make_shared()); + auto top = parser.parse_top(U("12345678901234")); + + VERIFY_ARE_EQUAL((int64_t)12345678901234LL, top); +} + +TEST(parse_skip_empty) +{ + odata_uri_parser parser(std::make_shared()); + auto skip = parser.parse_skip(::odata::utility::string_t()); + + VERIFY_IS_TRUE(null_value == skip); +} + +TEST(parse_skip_value) +{ + odata_uri_parser parser(std::make_shared()); + auto skip = parser.parse_skip(U("12345678901234")); + + VERIFY_ARE_EQUAL((int64_t)12345678901234LL, skip); +} + +TEST(parse_count_empty) +{ + odata_uri_parser parser(std::make_shared()); + auto count = parser.parse_count(::odata::utility::string_t()); + + VERIFY_IS_TRUE(nullptr == count); +} + +TEST(parse_count_true) +{ + odata_uri_parser parser(std::make_shared()); + auto count = parser.parse_count(U("true")); + + VERIFY_IS_TRUE(count); +} + +TEST(parse_count_false) +{ + odata_uri_parser parser(std::make_shared()); + auto count = parser.parse_count(U("false")); + + VERIFY_IS_FALSE(count); +} + +TEST(parse_invalid_count) +{ + odata_uri_parser parser(std::make_shared()); + + VERIFY_THROWS(parser.parse_count(U("abc")), odata_exception); +} + +TEST(parse_select_expand_empty) +{ + odata_uri_parser parser(std::make_shared()); + auto select_expand_clause = parser.parse_select_and_expand(::odata::utility::string_t(), ::odata::utility::string_t()); + + VERIFY_IS_TRUE(nullptr == select_expand_clause); +} + +TEST(parse_filter_empty) +{ + odata_uri_parser parser(std::make_shared()); + auto filter_clause = parser.parse_filter(::odata::utility::string_t()); + + VERIFY_IS_TRUE(nullptr == filter_clause); +} + +TEST(parse_orderby_empty) +{ + odata_uri_parser parser(std::make_shared()); + auto orderby_clause = parser.parse_orderby(::odata::utility::string_t()); + + VERIFY_IS_TRUE(nullptr == orderby_clause); +} + +TEST(parse_search_empty) +{ + odata_uri_parser parser(std::make_shared()); + auto search_clause = parser.parse_search(::odata::utility::string_t()); + + VERIFY_IS_TRUE(nullptr == search_clause); +} + +static void verify_range_variable(std::shared_ptr range_variable, const ::odata::utility::string_t &name, const ::odata::utility::string_t &type) +{ + VERIFY_ARE_EQUAL(name, range_variable->name()); + VERIFY_ARE_EQUAL(type, range_variable->target_type()->get_full_name()); + VERIFY_IS_TRUE(nullptr == range_variable->target_navigation_source()); +} + +static void verify_range_variable(std::shared_ptr range_variable, const ::odata::utility::string_t &name, const ::odata::utility::string_t &type, const ::odata::utility::string_t &source) +{ + VERIFY_ARE_EQUAL(name, range_variable->name()); + VERIFY_ARE_EQUAL(type, range_variable->target_type()->get_full_name()); + VERIFY_ARE_EQUAL(source, range_variable->target_navigation_source()->get_name()); +} + +TEST(parse_filter_after_entity_set) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$filter=ID gt 1")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_gt_node(filter_clause->expression()); + verify_range_variable(filter_clause->range_variable(), U("$it"), U("MyNS.Person"), U("People")); +} + +TEST(parse_filter_after_collection_navigation_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/Mates?$filter=ID gt 1")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_gt_node(filter_clause->expression()); + verify_range_variable(filter_clause->range_variable(), U("$it"), U("MyNS.Person"), U("People")); +} + +static void verify_is_operator_eq_node(std::shared_ptr node) +{ + VERIFY_ARE_EQUAL(odata_query_node_kind::BinaryOperator, node->node_kind()); + VERIFY_ARE_EQUAL(binary_operator_kind::Equal, node->as()->operator_kind()); +} + +TEST(parse_filter_after_collection_structural_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/FormerAddresses?$filter=Country eq 'abc'")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_eq_node(filter_clause->expression()); + verify_range_variable(filter_clause->range_variable(), U("$it"), U("MyNS.Address")); +} + +TEST(parse_filter_after_single_navigation_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/Parent?$filter=ID gt 1")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_filter_after_single_entity) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$filter=ID gt 1")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_filter_after_dollar_count) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People/$count?$filter=ID gt 1")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_filter_after_dollar_value) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/ID/$value?$filter=ID gt 1")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_filter_after_dollar_ref) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/ID/$ref?$filter=ID gt 1")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_filter_after_dollar_batch) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/$batch?$filter=ID gt 1")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_filter_after_dollar_metadata) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/$metadata?$filter=ID gt 1")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +static void verify_orderby_item(std::shared_ptr orderby_clause, ::size_t i, const ::odata::utility::string_t &property_name, bool asc) +{ + VERIFY_ARE_EQUAL(property_name, orderby_clause->item_at(i).first->as()->property_name()); + VERIFY_ARE_EQUAL(asc, orderby_clause->item_at(i).second); +} + +TEST(parse_orderby_after_entity_set) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$orderby=ID desc,Age asc")); + auto parsed_uri = parser.parse_uri(uri); + auto orderby_clause = parsed_uri->orderby_clause(); + + VERIFY_ARE_EQUAL(2, orderby_clause->items().size()); + verify_orderby_item(orderby_clause, 0, U("ID"), false); + verify_orderby_item(orderby_clause, 1, U("Age"), true); + + verify_range_variable(orderby_clause->range_variable(), U("$it"), U("MyNS.Person"), U("People")); +} + +TEST(parse_orderby_after_collection_navigation_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/Mates?$orderby=ID desc,Age asc")); + auto parsed_uri = parser.parse_uri(uri); + auto orderby_clause = parsed_uri->orderby_clause(); + + VERIFY_ARE_EQUAL(2, orderby_clause->items().size()); + verify_orderby_item(orderby_clause, 0, U("ID"), false); + verify_orderby_item(orderby_clause, 1, U("Age"), true); + + verify_range_variable(orderby_clause->range_variable(), U("$it"), U("MyNS.Person"), U("People")); +} + +TEST(parse_orderby_after_collection_structural_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/FormerAddresses?$orderby=Country")); + auto parsed_uri = parser.parse_uri(uri); + auto orderby_clause = parsed_uri->orderby_clause(); + + VERIFY_ARE_EQUAL(1, orderby_clause->items().size()); + verify_orderby_item(orderby_clause, 0, U("Country"), true); + + verify_range_variable(orderby_clause->range_variable(), U("$it"), U("MyNS.Address")); +} + +TEST(parse_orderby_after_single_navigation_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/Parent?$orderby=ID desc,Age asc")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_orderby_after_single_entity) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$orderby=ID desc,Age asc")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_orderby_after_dollar_count) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People/$count?$orderby=ID desc,Age asc")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_orderby_after_dollar_value) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/ID/$value?$orderby=ID desc,Age asc")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_orderby_after_dollar_ref) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)/ID/$ref?$orderby=ID desc,Age asc")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_orderby_after_dollar_batch) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/$batch?$orderby=ID desc,Age asc")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_orderby_after_dollar_metadata) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/$metadata?$orderby=ID desc,Age asc")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_search) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$search=ABC or BCD")); + auto parsed_uri = parser.parse_uri(uri); + auto search_clause = parsed_uri->search_clause(); + + verify_is_operator_or_node(search_clause->expression()); + + verify_is_string(search_clause->expression()->as()->left(), U("ABC")); + verify_is_string(search_clause->expression()->as()->right(), U("BCD")); +} + +TEST(parse_select_after_nonstructured_type) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People/ID?$select=ID")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_expand_after_nonstructured_type) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People/ID?$expand=Mates")); + + VERIFY_THROWS(parser.parse_uri(uri), odata_exception); +} + +TEST(parse_select_single_structural_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$select=ID")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(1, select_expand_clause->select_items().size()); + verify_is_property_access_node(select_expand_clause->select_item_at(0)->end(), U("ID")); +} + +TEST(parse_select_structural_properties) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$select=ID,Age")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(2, select_expand_clause->select_items().size()); + verify_is_property_access_node(select_expand_clause->select_item_at(0)->end(), U("ID")); + verify_is_property_access_node(select_expand_clause->select_item_at(1)->end(), U("Age")); +} + +TEST(parse_select_structural_property_path) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$select=ID,Address/Country")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(2, select_expand_clause->select_items().size()); + verify_is_property_access_node(select_expand_clause->select_item_at(0)->end(), U("ID")); + verify_is_property_access_node(select_expand_clause->select_item_at(1)->end(), U("Country")); + verify_is_property_access_node(select_expand_clause->select_item_at(1)->end()->as()->parent(), U("Address")); +} + +TEST(parse_star_in_select) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$select=*")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(1, select_expand_clause->select_items().size()); + verify_is_property_access_node(select_expand_clause->select_item_at(0)->end(), U("*")); +} + +TEST(parse_star_in_select_path) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$select=ID,Address/*")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(2, select_expand_clause->select_items().size()); + verify_is_property_access_node(select_expand_clause->select_item_at(0)->end(), U("ID")); + verify_is_property_access_node(select_expand_clause->select_item_at(1)->end(), U("*")); + verify_is_property_access_node(select_expand_clause->select_item_at(1)->end()->as()->parent(), U("Address")); +} + +TEST(parse_expand_single_navigation_property) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$expand=Mates")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(1, select_expand_clause->expand_items().size()); + verify_is_property_access_node(select_expand_clause->expand_item_at(0)->end(), U("Mates")); +} + +TEST(parse_expand_navigation_properties) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$expand=Mates,Friends")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(2, select_expand_clause->expand_items().size()); + verify_is_property_access_node(select_expand_clause->expand_item_at(0)->end(), U("Mates")); + verify_is_property_access_node(select_expand_clause->expand_item_at(1)->end(), U("Friends")); +} + +TEST(parse_expand_navigation_property_path) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$expand=Mates,Parent/Friends")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(2, select_expand_clause->expand_items().size()); + verify_is_property_access_node(select_expand_clause->expand_item_at(0)->end(), U("Mates")); + verify_is_property_access_node(select_expand_clause->expand_item_at(1)->end(), U("Friends")); + verify_is_property_access_node(select_expand_clause->expand_item_at(1)->end()->as()->parent(), U("Parent")); +} + +TEST(parse_star_in_expand) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$expand=*")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(1, select_expand_clause->expand_items().size()); + verify_is_property_access_node(select_expand_clause->expand_item_at(0)->end(), U("*")); +} + +TEST(parse_star_in_expand_path) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People(1)?$expand=Mates,Parent/*")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + + VERIFY_ARE_EQUAL(2, select_expand_clause->expand_items().size()); + verify_is_property_access_node(select_expand_clause->expand_item_at(0)->end(), U("Mates")); + verify_is_property_access_node(select_expand_clause->expand_item_at(1)->end(), U("*")); + verify_is_property_access_node(select_expand_clause->expand_item_at(1)->end()->as()->parent(), U("Parent")); +} + +TEST(parse_nested_expand_options) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$expand=Mates($select=ID;$expand=Friends($select=Age;$filter='abc()' eq 'abc()');$filter=Age gt 10;$orderby=Age asc;$top=10;$skip=1;$count=true),Parent($select=ID)")); + auto parsed_uri = parser.parse_uri(uri); + auto select_expand_clause = parsed_uri->select_expand_clause(); + VERIFY_ARE_EQUAL(2, select_expand_clause->expand_items().size()); + VERIFY_ARE_EQUAL(U("Mates"), select_expand_clause->expand_item_at(0)->end()->as()->property_name()); + VERIFY_IS_TRUE(select_expand_clause->expand_item_at(0)->select_expand_clause() != nullptr); + VERIFY_ARE_EQUAL(1, select_expand_clause->expand_item_at(0)->select_expand_clause()->select_items().size()); + VERIFY_ARE_EQUAL(1, select_expand_clause->expand_item_at(0)->select_expand_clause()->expand_items().size()); + VERIFY_IS_TRUE(select_expand_clause->expand_item_at(0)->filter_clause() != nullptr); + VERIFY_ARE_EQUAL(1, select_expand_clause->expand_item_at(0)->orderby_clause()->items().size()); + VERIFY_ARE_EQUAL(10, select_expand_clause->expand_item_at(0)->top().value()); + VERIFY_ARE_EQUAL(1, select_expand_clause->expand_item_at(0)->skip().value()); + VERIFY_IS_TRUE(select_expand_clause->expand_item_at(0)->count().value()); + + VERIFY_ARE_EQUAL(U("Parent"), select_expand_clause->expand_item_at(1)->end()->as()->property_name()); + VERIFY_IS_TRUE(select_expand_clause->expand_item_at(1)->select_expand_clause() != nullptr); + VERIFY_ARE_EQUAL(1, select_expand_clause->expand_item_at(1)->select_expand_clause()->select_items().size()); +} + +TEST(parse_filter_globalization_test_1) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$filter=ChineseName gt 'abc张三bcd李四efg'")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_gt_node(filter_clause->expression()); + auto binary_node = filter_clause->expression()->as(); + verify_is_property_access_node(binary_node->left(), U("ChineseName")); + verify_is_string(binary_node->right(), U("abc张三bcd李四efg")); +} + +TEST(parse_filter_globalization_test_2) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$filter=ChineseName gt 'abcにほんごbcdふりがなefg'")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_gt_node(filter_clause->expression()); + auto binary_node = filter_clause->expression()->as(); + verify_is_property_access_node(binary_node->left(), U("ChineseName")); + verify_is_string(binary_node->right(), U("abcにほんごbcdふりがなefg")); +} + +TEST(parse_filter_globalization_test_3) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$filter=ChineseName gt 'abc조선글bcd골프에efg'")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_gt_node(filter_clause->expression()); + auto binary_node = filter_clause->expression()->as(); + verify_is_property_access_node(binary_node->left(), U("ChineseName")); + verify_is_string(binary_node->right(), U("abc조선글bcd골프에efg")); +} + +TEST(parse_filter_globalization_test_4) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$filter=ChineseName gt 'Русскийязык'")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_gt_node(filter_clause->expression()); + auto binary_node = filter_clause->expression()->as(); + verify_is_property_access_node(binary_node->left(), U("ChineseName")); + verify_is_string(binary_node->right(), U("Русскийязык")); +} + +TEST(parse_filter_globalization_test_5) +{ + odata_uri_parser parser(get_test_model_with_person_with_one_key()); + auto uri = ::odata::utility::uri::encode_uri(U("http://service-root/People?$filter=ChineseName gt 'débutakönnten'")); + auto parsed_uri = parser.parse_uri(uri); + auto filter_clause = parsed_uri->filter_clause(); + + verify_is_operator_gt_node(filter_clause->expression()); + auto binary_node = filter_clause->expression()->as(); + verify_is_property_access_node(binary_node->left(), U("ChineseName")); + verify_is_string(binary_node->right(), U("débutakönnten")); +} + +// --END-- odata_query_option_parser + +// --BEGIN-- odata_path_segment_visitor + +class print_visitor : public odata_path_segment_visitor<::odata::utility::string_t> +{ +public: + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_metadata_segment> segment) + { + return U("Metadata"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_batch_segment> segment) + { + return U("Batch"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_entity_set_segment> segment) + { + return U("EntitySet"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_singleton_segment> segment) + { + return U("Singleton"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_key_segment> segment) + { + return U("Key"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_structural_property_segment> segment) + { + return U("StructuralProperty"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_navigation_property_segment> segment) + { + return U("NavigationProperty"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_dynamic_property_segment> segment) + { + return U("DynamicProperty"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_value_segment> segment) + { + return U("Value"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_count_segment> segment) + { + return U("Count"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_ref_segment> segment) + { + return U("Ref"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_type_segment> segment) + { + return U("Type"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_operation_segment> segment) + { + return U("Operation"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_operation_import_segment> segment) + { + return U("OperationImport"); + } + + ::odata::utility::string_t visit_any(std::shared_ptr<::odata::core::odata_path_segment> segment) + { + return U("Any"); + } +}; + +TEST(visit_odata_path) +{ + auto model = get_test_model_with_person_with_one_key(); + + std::vector> segments; + segments.push_back(odata_path_segment::create_metadata_segment()); + segments.push_back(odata_path_segment::create_batch_segment()); + segments.push_back(odata_path_segment::create_entity_set_segment(model->find_container()->find_entity_set(U("People")))); + segments.push_back(odata_path_segment::create_singleton_segment(model->find_container()->find_singleton(U("CEO")))); + segments.push_back(odata_path_segment::create_key_segment(nullptr, nullptr, std::vector>>())); + segments.push_back(odata_path_segment::create_structural_property_segment(nullptr, nullptr)); + segments.push_back(odata_path_segment::create_navigation_property_segment(nullptr, nullptr, nullptr)); + segments.push_back(odata_path_segment::create_dynamic_property_segment(U("DynamicProperty"))); + segments.push_back(odata_path_segment::create_value_segment()); + segments.push_back(odata_path_segment::create_count_segment()); + segments.push_back(odata_path_segment::create_ref_segment()); + segments.push_back(odata_path_segment::create_type_segment(nullptr)); + segments.push_back(odata_path_segment::create_operation_segment(nullptr, std::vector>>())); + segments.push_back(odata_path_segment::create_operation_import_segment(nullptr, std::vector>>())); + segments.push_back(std::make_shared()); + + odata_path path(segments); + auto visitor = std::make_shared(); + auto result = path.visit_with(std::static_pointer_cast>(visitor)); + + VERIFY_ARE_EQUAL(U("Metadata"), result[0]); + VERIFY_ARE_EQUAL(U("Batch"), result[1]); + VERIFY_ARE_EQUAL(U("EntitySet"), result[2]); + VERIFY_ARE_EQUAL(U("Singleton"), result[3]); + VERIFY_ARE_EQUAL(U("Key"), result[4]); + VERIFY_ARE_EQUAL(U("StructuralProperty"), result[5]); + VERIFY_ARE_EQUAL(U("NavigationProperty"), result[6]); + VERIFY_ARE_EQUAL(U("DynamicProperty"), result[7]); + VERIFY_ARE_EQUAL(U("Value"), result[8]); + VERIFY_ARE_EQUAL(U("Count"), result[9]); + VERIFY_ARE_EQUAL(U("Ref"), result[10]); + VERIFY_ARE_EQUAL(U("Type"), result[11]); + VERIFY_ARE_EQUAL(U("Operation"), result[12]); + VERIFY_ARE_EQUAL(U("OperationImport"), result[13]); + VERIFY_ARE_EQUAL(U("Any"), result[14]); +} + +// --END-- odata_path_segment_visitor + +// --BEGIN-- odata_query_node_visitor + +class to_string_visitor : public odata_query_node_visitor<::odata::utility::string_t> +{ +public: + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_constant_node> node) + { + return U("Constant"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_binary_operator_node> node) + { + return U("BinaryOperator"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_unary_operator_node> node) + { + return U("UnaryOperator"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_parameter_alias_node> node) + { + return U("ParameterAlias"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_property_access_node> node) + { + return U("PropertyAccess"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_type_cast_node> node) + { + return U("TypeCast"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_lambda_node> node) + { + return U("Lambda"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_range_variable_node> node) + { + return U("RangeVariable"); + } + + ::odata::utility::string_t visit(std::shared_ptr<::odata::core::odata_function_call_node> node) + { + return U("FunctionCall"); + } + + ::odata::utility::string_t visit_any(std::shared_ptr<::odata::core::odata_query_node> node) + { + return U("Any"); + } +}; + +TEST(visit_query_nodes) +{ + std::shared_ptr> visitor = std::make_shared(); + + auto constant = odata_query_node::create_constant_node(nullptr); + VERIFY_ARE_EQUAL(U("Constant"), constant->accept(visitor)); + + auto binary_operator = odata_query_node::create_operator_add_node(nullptr, nullptr); + VERIFY_ARE_EQUAL(U("BinaryOperator"), binary_operator->accept(visitor)); + + auto unary_operator = odata_query_node::create_operator_not_node(nullptr); + VERIFY_ARE_EQUAL(U("UnaryOperator"), unary_operator->accept(visitor)); + + auto parameter_alias = odata_query_node::create_parameter_alias_node(U("@word")); + VERIFY_ARE_EQUAL(U("ParameterAlias"), parameter_alias->accept(visitor)); + + auto property_access = odata_query_node::create_property_access_node(U("a"), nullptr); + VERIFY_ARE_EQUAL(U("PropertyAccess"), property_access->accept(visitor)); + + auto type_cast = odata_query_node::create_type_cast_node(U("b.b"), nullptr); + VERIFY_ARE_EQUAL(U("TypeCast"), type_cast->accept(visitor)); + + auto lambda = odata_query_node::create_lambda_node(false, nullptr, U("d"), nullptr); + VERIFY_ARE_EQUAL(U("Lambda"), lambda->accept(visitor)); + + auto range_variable = odata_query_node::create_range_variable_node(U("d")); + VERIFY_ARE_EQUAL(U("RangeVariable"), range_variable->accept(visitor)); + + auto function_call = odata_query_node::create_function_call_node(U("foo"), + std::vector>>()); + VERIFY_ARE_EQUAL(U("FunctionCall"), function_call->accept(visitor)); + + auto node = std::make_shared(); + VERIFY_ARE_EQUAL(U("Any"), node->accept(visitor)); +} + +// --END-- odata_query_node_visitor + +} + +}}} \ No newline at end of file diff --git a/tests/functional/core_test/odata_value_test.cpp b/tests/functional/core_test/odata_value_test.cpp new file mode 100644 index 0000000..ba86dc2 --- /dev/null +++ b/tests/functional/core_test/odata_value_test.cpp @@ -0,0 +1,446 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_reader.h" + +using namespace ::odata::edm; +using namespace ::odata::core; + +namespace tests { namespace functional { namespace _odata { + +SUITE(odata_value_test_cases) +{ + +TEST(primitive_value) +{ + auto int32 = odata_primitive_value::make_primitive_value(-32); + VERIFY_IS_NOT_NULL(int32); + VERIFY_ARE_EQUAL(int32->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + auto primitive_type = std::dynamic_pointer_cast(int32->get_value_type()); + VERIFY_IS_NOT_NULL(primitive_type); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int32); + + // make a binary data + unsigned char ini_binary[] = {'Q', 'B', 'A', 'B', 'A', 'C', 'X', '1', '2', '*', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'p'}; + std::vector binary(ini_binary, ini_binary + 25); + auto binary_input = odata_primitive_value::make_primitive_value(binary); + VERIFY_ARE_EQUAL(binary_input->to_string(), U("UUJBQkFDWDEyKjEyMzQ1Njc4OTBhYmNkZQ==")); + auto binary_output = binary_input->as>(); + VERIFY_ARE_EQUAL(binary_output[7], '1'); + + //int64 + auto i64 = odata_primitive_value::make_primitive_value((int64_t)-123123213); + VERIFY_ARE_EQUAL(i64->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(i64->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int64); + VERIFY_ARE_EQUAL(i64->as(), -123123213); + + //int16 + auto i16 = odata_primitive_value::make_primitive_value((int16_t)-256); + VERIFY_ARE_EQUAL(i16->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(i16->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int16); + VERIFY_ARE_EQUAL(i16->as(), -256); + + //double + auto double_v = odata_primitive_value::make_primitive_value((double)-12123.2312); + VERIFY_ARE_EQUAL(double_v->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(double_v->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Double); + VERIFY_ARE_EQUAL(double_v->as(), -12123.2312); + + //bool + bool v = false; + auto bool_v = odata_primitive_value::make_primitive_value(v); + VERIFY_ARE_EQUAL(int32->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(bool_v->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Boolean); + VERIFY_ARE_EQUAL(bool_v->as(), false); + + //date time + ::odata::utility::datetime dt = ::odata::utility::datetime::from_string(U("2014-5-13T11:40:00Z"), ::odata::utility::datetime::date_format::ISO_8601); + auto date_v = odata_primitive_value::make_primitive_value(dt); + VERIFY_ARE_EQUAL(date_v->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(date_v->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::DateTimeOffset); + auto ck_dt = date_v->as<::odata::utility::datetime>(); + VERIFY_ARE_EQUAL(ck_dt, dt); + + //duration + ::odata::utility::seconds sd(3600); + auto second_v = odata_primitive_value::make_primitive_value(sd); + VERIFY_ARE_EQUAL(second_v->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(second_v->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Duration); + auto ck_second = second_v->as<::odata::utility::seconds>(); + VERIFY_ARE_EQUAL(ck_second, sd); + + //float + auto float_v = odata_primitive_value::make_primitive_value((float)-121.2312); + VERIFY_ARE_EQUAL(float_v->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(float_v->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Single); + VERIFY_IS_TRUE(abs(float_v->as() - -121.2312) < 0.00001); + + //string + auto str_v = odata_primitive_value::make_primitive_value(U("test string")); + VERIFY_ARE_EQUAL(str_v->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + primitive_type = std::dynamic_pointer_cast(str_v->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::String); + VERIFY_ARE_EQUAL(str_v->as<::odata::utility::string_t>(), U("test string")); +} + +TEST(primitive_value_boundary_i64) +{ + + // min i64 + { + auto i64 = odata_primitive_value::make_primitive_value((int64_t)-9223372036854775808); + VERIFY_ARE_EQUAL(i64->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + auto primitive_type = std::dynamic_pointer_cast(i64->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int64); + VERIFY_IS_TRUE(i64->as() < 0); + VERIFY_ARE_EQUAL(i64->as(), (int64_t)-9223372036854775808); + } + + // max i64 + { + auto i64 = odata_primitive_value::make_primitive_value((int64_t)9223372036854775807); + VERIFY_ARE_EQUAL(i64->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + auto primitive_type = std::dynamic_pointer_cast(i64->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int64); + VERIFY_IS_TRUE(i64->as() > 0); + VERIFY_ARE_EQUAL(i64->as(), 9223372036854775807); + } + + //overflow + { + auto i64 = odata_primitive_value::make_primitive_value((int64_t)9923372036854775807); + VERIFY_ARE_EQUAL(i64->get_value_type()->get_type_kind(), edm_type_kind_t::Primitive); + auto primitive_type = std::dynamic_pointer_cast(i64->get_value_type()); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::Int64); + VERIFY_IS_TRUE(i64->as() < 0); + } +} + +TEST(primitive_value_boundary_i32) +{ + // max i32 2147483647 + { + auto i32 = odata_primitive_value::make_primitive_value((int32_t)2147483647); + VERIFY_IS_TRUE(i32->as() > 0); + VERIFY_ARE_EQUAL(i32->as(), 2147483647); + } + + // min i32 -2147483648 + { + auto i32 = odata_primitive_value::make_primitive_value((int32_t)-2147483648); + VERIFY_IS_TRUE(i32->as() < 0); + VERIFY_ARE_EQUAL(i32->as(), -2147483648); + } + + // overflow + { + auto i32 = odata_primitive_value::make_primitive_value((int32_t)2147483648); + VERIFY_IS_TRUE(i32->as() < 0); + } + + // overflow + { + auto i32 = odata_primitive_value::make_primitive_value((int32_t)-2147483649); + VERIFY_IS_TRUE(i32->as() > 0); + } +} + +TEST(primitive_value_boundary_i16) +{ + // max i16 32767 + { + auto i16 = odata_primitive_value::make_primitive_value((int16_t)32767); + VERIFY_IS_TRUE(i16->as() > 0); + VERIFY_ARE_EQUAL(i16->as(), 32767); + } + + // min i16 -32768 + { + auto i16 = odata_primitive_value::make_primitive_value((int16_t)-32768); + VERIFY_IS_TRUE(i16->as() < 0); + VERIFY_ARE_EQUAL(i16->as(), -32768); + } + + // overflow + { + auto i16 = odata_primitive_value::make_primitive_value((int16_t)32768); + VERIFY_IS_TRUE(i16->as() < 0); + } + + // overflow + { + auto i16 = odata_primitive_value::make_primitive_value((int16_t)-32769); + VERIFY_IS_TRUE(i16->as() > 0); + } +} + + +TEST(structured_value) +{ + std::shared_ptr<::odata::edm::edm_named_type> type = nullptr; + auto structurd_value = std::make_shared(type); + VERIFY_IS_NOT_NULL(structurd_value); + + int16_t i16 = -8; + structurd_value->set_value(U("int16"), i16); + int32_t i32 = -123; + structurd_value->set_value(U("int32"), i32); + int64_t i64 = -121321321321321; + structurd_value->set_value(U("int64"), i64); + unsigned char ini_binary[] = {'Q', 'B', 'A', 'B', 'A', 'C', 'X', '1', '2', '*'}; + std::vector binary(ini_binary, ini_binary + 10); + structurd_value->set_value(U("binary"), binary); + bool bl = true; + structurd_value->set_value(U("boolean"), bl); + ::odata::utility::datetime dt = ::odata::utility::datetime::from_string(U("2013-12-31T00:00:00Z"), ::odata::utility::datetime::date_format::ISO_8601); + structurd_value->set_value(U("datetime"), dt); + ::odata::utility::char_t* ch_str = U("char_t pointer input"); + structurd_value->set_value(U("char_t pointer"), ch_str); + ::odata::utility::string_t str = U("string_t input"); + structurd_value->set_value(U("string_t"), str); + double db = -32342212.23424; + structurd_value->set_value(U("double"), db); + + bool b_get = false; + b_get = structurd_value->try_get(U("int16"), i16); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(i16, -8); + b_get = structurd_value->try_get(U("int32"), i32); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(i32, -123); + b_get = structurd_value->try_get(U("int64"), i64); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(i64, -121321321321321); + b_get = structurd_value->try_get(U("binary"), binary); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(binary.size(), 10); + VERIFY_ARE_EQUAL(binary[5], 'C'); + b_get = structurd_value->try_get(U("boolean"), bl); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(bl, true); + b_get = structurd_value->try_get(U("datetime"), dt); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(dt.to_string(::odata::utility::datetime::date_format::ISO_8601), U("2013-12-31T00:00:00Z")); + b_get = structurd_value->try_get(U("char_t pointer"), str); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(str, U("char_t pointer input")); + b_get = structurd_value->try_get(U("string_t"), str); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(str, U("string_t input")); + b_get = structurd_value->try_get(U("double"), db); + VERIFY_ARE_EQUAL(b_get, true); + double as = abs(db - -32342212.23424); + VERIFY_ARE_EQUAL(as < 0.000001, true); +} + +TEST(complex_value) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + + auto complex_value = std::make_shared(model->find_complex_type(U("AccountInfo"))); + complex_value->set_value(U("FirstName"), U("Leo")); + complex_value->set_value(U("LastName"), U("Hu")); + + std::shared_ptr<::odata::edm::edm_named_type> type = nullptr; + auto structurd_value = std::make_shared(type); + VERIFY_IS_NOT_NULL(structurd_value); + structurd_value->set_value(U("complex value"), complex_value); + + bool b_get = false; + std::shared_ptr verify = nullptr; + b_get = structurd_value->try_get(U("complex value"), verify); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_IS_NOT_NULL(verify); + VERIFY_ARE_EQUAL(verify->properties().size(), 2); + ::odata::utility::string_t str; + verify->try_get(U("FirstName"), str); + VERIFY_ARE_EQUAL(str, U("Leo")); + verify->try_get(U("LastName"), str); + VERIFY_ARE_EQUAL(str, U("Hu")); +} + +TEST(entity_value) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + + auto entity_value = std::make_shared(model->find_entity_type(U("Account"))); + entity_value->set_value(U("AccountID"), (int32_t)100); + entity_value->set_value(U("Country"), U("China")); + + std::shared_ptr<::odata::edm::edm_named_type> type = nullptr; + auto structurd_value = std::make_shared(type); + VERIFY_IS_NOT_NULL(structurd_value); + structurd_value->set_value(U("entity value"), entity_value); + + bool b_get = false; + std::shared_ptr verify = nullptr; + b_get = structurd_value->try_get(U("entity value"), verify); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_IS_NOT_NULL(verify); + VERIFY_ARE_EQUAL(verify->properties().size(), 2); + int32_t id; + verify->try_get(U("AccountID"), id); + VERIFY_ARE_EQUAL(id, 100); + ::odata::utility::string_t str; + verify->try_get(U("Country"), str); + VERIFY_ARE_EQUAL(str, U("China")); +} + +TEST(enum_value) +{ + auto model = get_test_model(); + VERIFY_IS_NOT_NULL(model); + + auto enum_value = std::make_shared(model->find_enum_type(U("AccessLevel")), U("Read")); + VERIFY_IS_NOT_NULL(enum_value); + + std::shared_ptr<::odata::edm::edm_named_type> type = nullptr; + auto structurd_value = std::make_shared(type); + VERIFY_IS_NOT_NULL(structurd_value); + structurd_value->set_value(U("enum value"), enum_value); + + bool b_get = false; + std::shared_ptr verify = nullptr; + b_get = structurd_value->try_get(U("enum value"), verify); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_IS_NOT_NULL(verify); + VERIFY_ARE_EQUAL(verify->to_string(), U("Read")); +} + +TEST(collection_value) +{ + auto model = get_test_model(); + + auto enum_value_1 = std::make_shared(model->find_enum_type(U("AccessLevel")), U("Read")); + auto enum_value_2 = std::make_shared(model->find_enum_type(U("AccessLevel")), U("Write")); + auto enum_value_3 = std::make_shared(model->find_enum_type(U("AccessLevel")), U("ReadWrite")); + + auto collection_type = std::make_shared(model->find_enum_type(U("AccessLevel"))); + auto collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(enum_value_1); + collection_value->add_collection_value(enum_value_2); + collection_value->add_collection_value(enum_value_3); + + + std::shared_ptr<::odata::edm::edm_named_type> type = nullptr; + auto structurd_value = std::make_shared(type); + VERIFY_IS_NOT_NULL(structurd_value); + structurd_value->set_value(U("collection value"), collection_value); + + bool b_get = false; + std::shared_ptr verify = nullptr; + b_get = structurd_value->try_get(U("collection value"), verify); + VERIFY_ARE_EQUAL(b_get, true); + VERIFY_ARE_EQUAL(verify->get_collection_values().size(), 3); + enum_value_1 = std::dynamic_pointer_cast(verify->get_collection_values()[0]); + VERIFY_ARE_EQUAL(enum_value_1->to_string(), U("Read")); + enum_value_2 = std::dynamic_pointer_cast(verify->get_collection_values()[1]); + VERIFY_ARE_EQUAL(enum_value_2->to_string(), U("Write")); + enum_value_3 = std::dynamic_pointer_cast(verify->get_collection_values()[2]); + VERIFY_ARE_EQUAL(enum_value_3->to_string(), U("ReadWrite")); +} + +TEST(primitive_collection_value) +{ + auto model = get_test_model(); + + auto p_value_1 = odata_primitive_value::make_primitive_value((double)-12123.2312); + auto p_value_2 = odata_primitive_value::make_primitive_value((double)-123123213); + auto p_value_3 = odata_primitive_value::make_primitive_value((double)-121.2312); + + auto collection_type = std::make_shared(::odata::edm::edm_primitive_type::DOUBLE()); + auto collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(p_value_1); + collection_value->add_collection_value(p_value_2); + collection_value->add_collection_value(p_value_3); + + p_value_1 = std::dynamic_pointer_cast(collection_value->get_collection_values()[0]); + VERIFY_IS_TRUE(abs(p_value_1->as() - -12123.2312) < 0.000001); + p_value_2 = std::dynamic_pointer_cast(collection_value->get_collection_values()[1]); + VERIFY_IS_TRUE(abs(p_value_2->as() - -123123213) < 0.000001); + p_value_3 = std::dynamic_pointer_cast(collection_value->get_collection_values()[2]); + VERIFY_IS_TRUE(abs(p_value_3->as() - -121.2312) < 0.000001); +} + +TEST(complex_collection_value) +{ + auto model = get_test_model(); + + auto complex_value_1 = std::make_shared(model->find_complex_type(U("AccountInfo"))); + complex_value_1->set_value(U("FirstName"), U("F_1")); + complex_value_1->set_value(U("LastName"), U("L_1")); + + auto collection_type = std::make_shared(model->find_complex_type(U("AccountInfo"))); + auto collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(complex_value_1); + + complex_value_1 = std::dynamic_pointer_cast(collection_value->get_collection_values()[0]); + ::odata::utility::string_t str; + complex_value_1->try_get(U("FirstName"), str); + VERIFY_ARE_EQUAL(str, U("F_1")); + complex_value_1->try_get(U("LastName"), str); + VERIFY_ARE_EQUAL(str, U("L_1")); +} + +TEST(entity_collection_value) +{ + auto model = get_test_model(); + + auto entity_value_1 = std::make_shared(model->find_entity_type(U("Account"))); + entity_value_1->set_value(U("AccountID"), (int32_t)100); + entity_value_1->set_value(U("Country"), U("China")); + + auto entity_value_2 = std::make_shared(model->find_entity_type(U("Account"))); + entity_value_2->set_value(U("AccountID"), (int32_t)200); + entity_value_2->set_value(U("Country"), U("JP")); + + auto entity_value_3 = std::make_shared(model->find_entity_type(U("Account"))); + entity_value_3->set_value(U("AccountID"), (int32_t)300); + entity_value_3->set_value(U("Country"), U("UK")); + + auto collection_type = std::make_shared(model->find_entity_type(U("Account"))); + auto collection_value = std::make_shared(collection_type); + collection_value->add_collection_value(entity_value_1); + collection_value->add_collection_value(entity_value_2); + collection_value->add_collection_value(entity_value_3); + + entity_value_1 = std::dynamic_pointer_cast(collection_value->get_collection_values()[0]); + entity_value_2 = std::dynamic_pointer_cast(collection_value->get_collection_values()[1]); + entity_value_3 = std::dynamic_pointer_cast(collection_value->get_collection_values()[2]); + + int32_t id; + ::odata::utility::string_t str; + entity_value_1->try_get(U("AccountID"), id); + VERIFY_ARE_EQUAL(id, 100); + entity_value_1->try_get(U("Country"), str); + VERIFY_ARE_EQUAL(str, U("China")); + + entity_value_2->try_get(U("AccountID"), id); + VERIFY_ARE_EQUAL(id, 200); + entity_value_2->try_get(U("Country"), str); + VERIFY_ARE_EQUAL(str, U("JP")); + + entity_value_3->try_get(U("AccountID"), id); + VERIFY_ARE_EQUAL(id, 300); + entity_value_3->try_get(U("Country"), str); + VERIFY_ARE_EQUAL(str, U("UK")); +} + +} + +}}} \ No newline at end of file diff --git a/tests/functional/edm_test/edm_model_reader_test.cpp b/tests/functional/edm_test/edm_model_reader_test.cpp new file mode 100644 index 0000000..82c5743 --- /dev/null +++ b/tests/functional/edm_test/edm_model_reader_test.cpp @@ -0,0 +1,251 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_reader.h" + +#ifdef __APPLE__ +extern "C" UnitTest::TestList& UnitTest::GetTestList() +{ + static TestList s_list; + return s_list; +} +#endif + +using namespace ::odata::edm; + +namespace tests { namespace functional { namespace _odata { + +const char* edm_model_version_test_string = +" \ + \ + \ + \ +"; + +const char* edm_model_enum_type_test_string = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +const char* edm_model_complex_type_test_string = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +const char* edm_model_entity_type_test_string = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +const char* edm_model_collection_of_complex_type_test_string = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +const char* edm_model_collection_of_navigation_type_test_string = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +SUITE(odata_edm_model_reader_tests) +{ + // /name:odata_edm_model_reader_tests:* + +TEST(collection_of_navigation_type) +{ + //auto model_reader = std::make_shared(edm_model_collection_of_navigation_type_test_string, strlen(edm_model_collection_of_navigation_type_test_string)); + //model_reader->parse(); + //auto model = model_reader->get_model(); + + std::istringstream iss(std::move(std::string(test_model_string))); + auto model_reader = std::make_shared(iss); + model_reader->parse(); + auto model = model_reader->get_model(); +} + +TEST(collection_of_complex_type) +{ + std::istringstream iss(std::move(std::string(edm_model_collection_of_complex_type_test_string))); + auto model_reader = std::make_shared(iss); + model_reader->parse(); + auto model = model_reader->get_model(); + + VERIFY_IS_NOT_NULL(model); + auto enity_type = model->find_entity_type(U("Person")); + VERIFY_IS_NOT_NULL(enity_type); + auto addresses = enity_type->find_property(U("Addresses")); + VERIFY_ARE_EQUAL(addresses->get_property_type()->get_type_kind(), edm_type_kind_t::Collection); + auto collection_type = std::dynamic_pointer_cast(addresses->get_property_type()); + auto collection_element_type = collection_type->get_element_type(); + VERIFY_ARE_EQUAL(collection_element_type->get_type_kind(), edm_type_kind_t::Complex); + VERIFY_ARE_EQUAL(collection_element_type->get_full_name(), U("Microsoft.Test.OData.Services.ODataWCFService.Address")); +} + + +TEST(edm_model_reader_entity_type_test) +{ + std::istringstream iss(std::move(std::string(edm_model_entity_type_test_string))); + auto model_reader = std::make_shared(iss); + model_reader->parse(); + auto model = model_reader->get_model(); + VERIFY_IS_NOT_NULL(model); + auto enity_type = model->find_entity_type(U("Person")); + VERIFY_IS_NOT_NULL(enity_type); + VERIFY_ARE_EQUAL(enity_type->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(enity_type->key().size(), 1); + VERIFY_ARE_EQUAL(enity_type->key()[0], U("PersonID")); + auto property_home_address = enity_type->find_property(U("HomeAddress")); + VERIFY_IS_NOT_NULL(property_home_address); + VERIFY_ARE_EQUAL(property_home_address->get_property_type()->get_type_kind(), edm_type_kind_t::Complex); + auto navigation_property = enity_type->find_property(U("Parent")); + VERIFY_IS_NOT_NULL(navigation_property); + VERIFY_ARE_EQUAL(navigation_property->get_property_type()->get_type_kind(), edm_type_kind_t::Navigation); + auto navigation_type = std::dynamic_pointer_cast(navigation_property->get_property_type()); + VERIFY_IS_NOT_NULL(navigation_type); + VERIFY_ARE_EQUAL(navigation_type->get_navigation_type()->get_type_kind(), edm_type_kind_t::Entity); + VERIFY_ARE_EQUAL(navigation_type->get_navigation_type()->get_name(), U("Person")); +} + + +TEST(edm_model_reader_complex_type_test) +{ + std::istringstream iss(std::move(std::string(edm_model_complex_type_test_string))); + auto model_reader = std::make_shared(iss); + model_reader->parse(); + auto model = model_reader->get_model(); + VERIFY_IS_NOT_NULL(model); + auto complex_type = model->find_complex_type(U("AccountInfo")); + VERIFY_IS_NOT_NULL(complex_type); + VERIFY_ARE_EQUAL(complex_type->get_type_kind(), edm_type_kind_t::Complex); + auto property_last_name = complex_type->find_property(U("LastName")); + VERIFY_IS_NOT_NULL(property_last_name); + VERIFY_ARE_EQUAL(property_last_name->is_nullable(), false); + auto property_type = property_last_name->get_property_type(); + VERIFY_IS_NOT_NULL(property_type); + VERIFY_ARE_EQUAL(property_type->get_type_kind(), edm_type_kind_t::Primitive); + auto primitive_type = std::dynamic_pointer_cast(property_type); + VERIFY_IS_NOT_NULL(primitive_type); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_kind_t::String); +} + +TEST(edm_model_reader_enum_type_test) +{ + std::istringstream iss(std::move(std::string(edm_model_enum_type_test_string))); + auto model_reader = std::make_shared(iss); + model_reader->parse(); + auto model = model_reader->get_model(); + VERIFY_IS_NOT_NULL(model); + auto enum_type = model->find_enum_type(U("AccessLevel")); + VERIFY_IS_NOT_NULL(enum_type); + VERIFY_ARE_EQUAL(enum_type->get_type_kind(), edm_type_kind_t::Enum); + VERIFY_ARE_EQUAL(enum_type->get_enum_members().size(), 5); + VERIFY_ARE_EQUAL(enum_type->is_flag(), true); +} + +TEST(edm_model_reader_version_test) +{ + std::istringstream iss(std::move(std::string(edm_model_version_test_string))); + auto model_reader = std::make_shared(iss); + model_reader->parse(); + auto model = model_reader->get_model(); + VERIFY_IS_NOT_NULL(model); + auto version = model->get_version(); + VERIFY_ARE_EQUAL(version, U("4.0")); +} + +} + +}}} diff --git a/tests/functional/edm_test/edm_model_utility_test.cpp b/tests/functional/edm_test/edm_model_utility_test.cpp new file mode 100644 index 0000000..68a5e3d --- /dev/null +++ b/tests/functional/edm_test/edm_model_utility_test.cpp @@ -0,0 +1,216 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "../odata_tests.h" +#include "odata/common/json.h" +#include "odata/edm/odata_edm.h" +#include "odata/edm/edm_model_utility.h" + +using namespace ::odata::edm; + +namespace tests { namespace functional { namespace _odata { + +struct edm_type_comparision_object +{ + ::odata::utility::string_t primitive_name; + edm_primitive_type_kind_t expected_primitive_type; +}; + +#define edm_primitive_type_test_contents_size 15 +static edm_type_comparision_object edm_primitive_type_test_contents[edm_primitive_type_test_contents_size] = +{ + {U("Edm.Binary"), edm_primitive_type_kind_t::Binary}, + {U("Edm.Boolean"), edm_primitive_type_kind_t::Boolean}, + {U("Edm.Byte"), edm_primitive_type_kind_t::Byte}, + {U("Edm.Duration"), edm_primitive_type_kind_t::Duration}, + {U("Edm.DateTimeOffset"), edm_primitive_type_kind_t::DateTimeOffset}, + {U("Edm.Double"), edm_primitive_type_kind_t::Double}, + {U("Edm.Decimal"), edm_primitive_type_kind_t::Decimal}, + {U("Edm.Guid"), edm_primitive_type_kind_t::Guid}, + {U("Edm.Int16"), edm_primitive_type_kind_t::Int16}, + {U("Edm.Int32"), edm_primitive_type_kind_t::Int32}, + {U("Edm.Int64"), edm_primitive_type_kind_t::Int64}, + {U("Edm.SByte"), edm_primitive_type_kind_t::SByte}, + {U("Edm.Single"), edm_primitive_type_kind_t::Single}, + {U("Edm.Stream"), edm_primitive_type_kind_t::Stream}, + {U("Edm.String"), edm_primitive_type_kind_t::String}, +}; + +SUITE(odata_edm_model_utility_tests) +{ + +TEST(get_edm_primitive_type_from_name) +{ + for (int i = 0; i < edm_primitive_type_test_contents_size; i++) + { + auto return_type = edm_model_utility::get_edm_primitive_type_from_name(edm_primitive_type_test_contents[i].primitive_name); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Primitive); + auto primitive_type = std::dynamic_pointer_cast(return_type); + VERIFY_IS_NOT_NULL(primitive_type); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), edm_primitive_type_test_contents[i].expected_primitive_type); + } +} + +TEST(get_edm_primitive_type_from_name_nagative_case) +{ + auto return_type = edm_model_utility::get_edm_primitive_type_from_name(U("")); + VERIFY_IS_NULL(return_type); + return_type = edm_model_utility::get_edm_primitive_type_from_name(U("Edm.")); + VERIFY_IS_NULL(return_type); + return_type = edm_model_utility::get_edm_primitive_type_from_name(U("Edm.Nothing")); + VERIFY_IS_NULL(return_type); +} + +struct get_edm_type_from_name_test_object +{ + ::odata::utility::string_t input_name; + edm_type_kind_t expected_edm_type; + edm_primitive_type_kind_t expected_primitive_type; +}; + +#define get_edm_type_from_name_test_size 15 +static get_edm_type_from_name_test_object get_edm_type_from_name_test_content[get_edm_type_from_name_test_size] = +{ + {U("Collection(Edm.Binary)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Binary}, + {U("Collection(Edm.Boolean)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Boolean}, + {U("Collection(Edm.Byte)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Byte}, + {U("Collection(Edm.Duration)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Duration}, + {U("Collection(Edm.DateTimeOffset)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::DateTimeOffset}, + {U("Collection(Edm.Double)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Double}, + {U("Collection(Edm.Decimal)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Decimal}, + {U("Collection(Edm.Guid)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Guid}, + {U("Collection(Edm.Int16)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Int16}, + {U("Collection(Edm.Int32)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Int32}, + {U("Collection(Edm.Int64)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Int64}, + {U("Collection(Edm.SByte)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::SByte}, + {U("Collection(Edm.Single)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Single}, + {U("Collection(Edm.Stream)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::Stream}, + {U("Collection(Edm.String)"), edm_type_kind_t::Collection, edm_primitive_type_kind_t::String}, +}; + +TEST(get_edm_type_from_name_collection_primitive_case) +{ + for (int i = 0; i < get_edm_type_from_name_test_size; i++) + { + auto return_type = edm_model_utility::get_edm_type_from_name(get_edm_type_from_name_test_content[i].input_name); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), get_edm_type_from_name_test_content[i].expected_edm_type); + auto collection_type = std::dynamic_pointer_cast(return_type); + VERIFY_IS_NOT_NULL(collection_type); + auto element_type = collection_type->get_element_type(); + VERIFY_IS_NOT_NULL(element_type); + auto primitive_type = std::dynamic_pointer_cast(element_type); + VERIFY_ARE_EQUAL(primitive_type->get_primitive_kind(), get_edm_type_from_name_test_content[i].expected_primitive_type); + } +} + +TEST(get_edm_type_from_name_collection_undetermined_case) +{ + auto return_type = edm_model_utility::get_edm_type_from_name(U("Collection(OData.Test)")); + VERIFY_IS_NOT_NULL(return_type); + VERIFY_ARE_EQUAL(return_type->get_type_kind(), edm_type_kind_t::Collection); + auto collection_type = std::dynamic_pointer_cast(return_type); + VERIFY_IS_NOT_NULL(collection_type); + auto element_type = collection_type->get_element_type(); + VERIFY_IS_NULL(element_type); +} + +TEST(get_edm_type_from_name_nagative_case) +{ + auto return_type = edm_model_utility::get_edm_type_from_name(U("OData.Test")); + VERIFY_IS_NULL(return_type); +} + +TEST(get_primitive_kind_from_edm_type) +{ + auto primitive_type = edm_primitive_type::BINARY(); + auto complex_type = std::make_shared(U(""), U("")); + auto entity_type = std::make_shared(U(""), U("")); + + edm_primitive_type_kind_t out_put; + bool ok = edm_model_utility::get_primitive_kind_from_edm_type(primitive_type, out_put); + VERIFY_ARE_EQUAL(ok, true); + VERIFY_ARE_EQUAL(out_put, edm_primitive_type_kind_t::Binary); + + ok = edm_model_utility::get_primitive_kind_from_edm_type(complex_type, out_put); + VERIFY_ARE_EQUAL(ok, false); + + ok = edm_model_utility::get_primitive_kind_from_edm_type(entity_type, out_put); + VERIFY_ARE_EQUAL(ok, false); +} + +TEST(resolve_edm_types_after_parsing_type_in_entity) +{ + ::odata::utility::string_t name_space = U("odata.functional.test"); + auto model = std::make_shared(); + auto schema = model->add_schema(U("schema"), name_space); + auto complex_address = std::make_shared(U("complex_address"), name_space); + auto entity_account = std::make_shared(U("entity_account"), name_space); + auto property_address = std::make_shared(U("Address")); + property_address->set_property_type(std::make_shared(U("complex_address"), U(""), edm_type_kind_t::Unknown)); + entity_account->add_property(property_address); + schema->add_complex_type(complex_address); + schema->add_entity_type(entity_account); + edm_model_utility::resolve_edm_types_after_parsing(model); + + auto prop = entity_account->find_property(U("Address")); + VERIFY_IS_NOT_NULL(prop); + auto prop_type = prop->get_property_type(); + VERIFY_IS_NOT_NULL(prop_type); + VERIFY_ARE_EQUAL(prop_type->get_type_kind(), edm_type_kind_t::Complex); +} + +TEST(resolve_edm_types_after_parsing_type_in_complex) +{ + ::odata::utility::string_t name_space = U("odata.functional.test"); + auto model = std::make_shared(); + auto schema = model->add_schema(U("schema"), name_space); + auto complex_address = std::make_shared(U("complex_address"), name_space); + auto complex_account = std::make_shared(U("complex_account"), name_space); + auto property_address = std::make_shared(U("Address")); + property_address->set_property_type(std::make_shared(U("complex_address"), U(""), edm_type_kind_t::Unknown)); + complex_account->add_property(property_address); + schema->add_complex_type(complex_address); + schema->add_complex_type(complex_account); + edm_model_utility::resolve_edm_types_after_parsing(model); + + auto prop = complex_account->find_property(U("Address")); + VERIFY_IS_NOT_NULL(prop); + auto prop_type = prop->get_property_type(); + VERIFY_IS_NOT_NULL(prop_type); + VERIFY_ARE_EQUAL(prop_type->get_type_kind(), edm_type_kind_t::Complex); +} + +TEST(resolve_edm_types_after_parsing_type_in_operation) +{ + ::odata::utility::string_t name_space = U("odata.functional.test"); + auto model = std::make_shared(); + auto schema = model->add_schema(U("schema"), name_space); + std::shared_ptr operation; + operation.reset(new edm_operation_type(U("operation"), name_space, false, U(""), EdmOperationKind::Function, false)); + auto complex_address = std::make_shared(U("complex_address"), name_space); + auto complex_account = std::make_shared(U("complex_account"), name_space); + operation->set_return_type(std::make_shared(U("complex_address"), U(""), edm_type_kind_t::Unknown)); + auto param = std::make_shared(); + param->set_param_type(std::make_shared(U("complex_account"), U(""), edm_type_kind_t::Unknown)); + operation->add_operation_parameter(param); + schema->add_complex_type(complex_address); + schema->add_complex_type(complex_account); + schema->add_operation_type(operation); + edm_model_utility::resolve_edm_types_after_parsing(model); + + auto verify_return_type = operation->get_operation_return_type(); + VERIFY_IS_NOT_NULL(verify_return_type); + VERIFY_ARE_EQUAL(verify_return_type->get_type_kind(), edm_type_kind_t::Complex); + auto verify_param = operation->get_operation_parameters()[0]; + VERIFY_IS_NOT_NULL(verify_param); + VERIFY_ARE_EQUAL(verify_param->get_param_type()->get_type_kind(), edm_type_kind_t::Complex); +} + +} + +}}} \ No newline at end of file diff --git a/tests/functional/odata_tests.cpp b/tests/functional/odata_tests.cpp new file mode 100644 index 0000000..10fb5c1 --- /dev/null +++ b/tests/functional/odata_tests.cpp @@ -0,0 +1,395 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata_tests.h" + +// only used for functional test which is mannualy changed that is different from the original edm model +const char* test_model_string = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + " + " \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + +std::shared_ptr<::odata::edm::edm_model> g_test_model; +::odata::utility::string_t g_service_root_url = U("http://odatae2etest.azurewebsites.net/cpptest/DefaultService"); +std::shared_ptr<::odata::edm::edm_model> get_test_model() +{ + if (!g_test_model) + { + std::istringstream iss(std::move(std::string(test_model_string))); + auto model_reader = std::make_shared<::odata::edm::edm_model_reader>(iss); + + model_reader->parse(); + g_test_model = model_reader->get_model(); + } + + return g_test_model; +} \ No newline at end of file diff --git a/tests/functional/odata_tests.h b/tests/functional/odata_tests.h new file mode 100644 index 0000000..621faf0 --- /dev/null +++ b/tests/functional/odata_tests.h @@ -0,0 +1,24 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/core/odata_core.h" +#include "odata/edm/odata_edm.h" +#include "odata/common/utility.h" +#include "odata/common/uri.h" +#include "odata/common/asyncrt_utils.h" +#include "odata/common/json.h" +//#include "odata/common/http_client.h" + +#include "unittestpp.h" +#include "os_utilities.h" + +extern const char* test_model_string; +extern std::shared_ptr<::odata::edm::edm_model> g_test_model; +extern ::odata::utility::string_t g_service_root_url; + +std::shared_ptr<::odata::edm::edm_model> get_test_model(); \ No newline at end of file diff --git a/tests/service/odata_select_expand_clause.cpp b/tests/service/odata_select_expand_clause.cpp new file mode 100644 index 0000000..08cb5b7 --- /dev/null +++ b/tests/service/odata_select_expand_clause.cpp @@ -0,0 +1,21 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata_select_expand_clause.h" + +namespace odata { namespace service +{ + +odata_select_expand_clause::odata_select_expand_clause() +{ +} + + +odata_select_expand_clause::~odata_select_expand_clause() +{ +} + +}} \ No newline at end of file diff --git a/tests/service/odata_select_expand_clause.h b/tests/service/odata_select_expand_clause.h new file mode 100644 index 0000000..f9e07d1 --- /dev/null +++ b/tests/service/odata_select_expand_clause.h @@ -0,0 +1,19 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +namespace odata { namespace service +{ + +class odata_select_expand_clause +{ +public: + odata_select_expand_clause(); + ~odata_select_expand_clause(); +}; + +}} \ No newline at end of file diff --git a/tests/service/odata_service_exception.h b/tests/service/odata_service_exception.h new file mode 100644 index 0000000..d85765f --- /dev/null +++ b/tests/service/odata_service_exception.h @@ -0,0 +1,44 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#pragma once + +#include "odata/common/utility.h" +#include "cpprest/asyncrt_utils.h" + +namespace odata { namespace service +{ + +/// +/// Represents an OData service operation exception +/// +class odata_service_exception : public std::exception +{ +public: + + /// + /// Constructor + /// + /// A string value containing the service error. + explicit odata_service_exception(::odata::utility::string_t error) : m_error(error) + { + } + + /// + /// Destructor + /// + ~odata_service_exception() _noexcept {} + + const ::odata::utility::string_t& what() + { + return m_error; + } + +private: + ::odata::utility::string_t m_error; +}; + +}} \ No newline at end of file diff --git a/tests/service/odata_test_service.cpp b/tests/service/odata_test_service.cpp new file mode 100644 index 0000000..97f0ae2 --- /dev/null +++ b/tests/service/odata_test_service.cpp @@ -0,0 +1,250 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "odata_test_service.h" + +using namespace web; +using namespace http; +using namespace utility; +using namespace http::experimental::listener; +using namespace odata::edm; +using namespace odata::core; + +namespace odata { namespace service +{ + + // TODO replace ::odata::utility::string_t with web::url + odata_test_service::odata_test_service(::odata::utility::string_t url, std::shared_ptr<::odata::edm::edm_model> model, std::shared_ptr service_document) + : m_listener(url), m_model(model), m_service_document(service_document) + { + m_uri_parser = std::make_shared<::odata::core::odata_uri_parser>(model); + m_service_root = web::uri(url); + m_listener.support(methods::GET, std::bind(&odata_test_service::handle_get, this, std::placeholders::_1)); + } + + void odata_test_service::handle_error(pplx::task& t) + { + try + { + t.get(); + } + catch(...) + { + // Ignore the error, Log it if a logger is available + } + } + + pplx::task odata_test_service::open() + { + return m_listener.open().then(std::bind(&handle_error, std::placeholders::_1)); + } + + pplx::task odata_test_service::close() + { + return m_listener.close().then(std::bind(&handle_error, std::placeholders::_1)); + } + + // Handler to process HTTP::GET requests. + // Replies to the request with data. + void odata_test_service::handle_get(http_request message) + { + try + { + bool is_async = false; + auto prefer_header = message.headers().find(U("Prefer")); + if (prefer_header != message.headers().end()) + { + if (prefer_header->second.find(U("respond-async")) != ::odata::utility::string_t::npos) + { + is_async = true; + } + } + + + auto parsed_uri = m_uri_parser->parse_uri(message.relative_uri()); + + odata_message_writer writer(m_model, m_service_root); + odata_context_url_builder context_url_builder(m_model, m_service_root); + odata_metadata_builder metadata_builder(m_model, m_service_root); + ::odata::utility::string_t content; + if (parsed_uri->is_service_document()) + { + // Write service document + content = writer.write_service_document(m_service_document); + } + else if (parsed_uri->is_metadata_document()) + { + // Write metadata document + content = writer.write_metadata_document(); + } + else + { + if (parsed_uri->path()->size() >= 1) + { + if (parsed_uri->path()->segment_at(0)->segment_type() == odata_path_segment_type::EntitySet) + { + auto entity_set_segment = parsed_uri->path()->segment_at(0)->as(); + if (entity_set_segment->entity_set()->get_name() == U("People")) + { + if (parsed_uri->path()->size() == 1) + { + auto people = get_people(); + auto context_url = context_url_builder.get_context_uri_for_collection_of_entities(entity_set_segment->entity_set()); + people->set_context_url(context_url); + + if (is_async) + { + std::unordered_map headers; + headers[U("OData-Version")] = U("4.0"); + headers[U("Content-Type")] = U("application/json;odata.metadata=full"); + + content = writer.write_asynchronous_odata_value(people, 200, U("OK"), headers); + } + else + { + content = writer.write_odata_value(people); + } + } + else if (parsed_uri->path()->segment_at(1)->segment_type() == odata_path_segment_type::Key) + { + auto key_segment = parsed_uri->path()->segment_at(1)->as(); + auto key = key_segment->keys()[0].second->as<::odata::utility::string_t>(); + + auto single_person = get_single_people(key); + single_person->set_is_top_level(true); + auto context_url = context_url_builder.get_context_uri_for_entity(entity_set_segment->entity_set()); + single_person->set_context_url(context_url); + + auto id = metadata_builder.get_entity_id(single_person, entity_set_segment->entity_set()); + single_person->set_id(id); + + auto read_link = metadata_builder.get_read_link(single_person, entity_set_segment->entity_set()); + single_person->set_read_link(read_link); + + auto edit_link = metadata_builder.get_edit_link(single_person, entity_set_segment->entity_set()); + single_person->set_edit_link(edit_link); + + + + content = writer.write_odata_value(single_person); + } + } + } + } + } + + message.reply(status_codes::OK, content).then(std::bind(&handle_error, std::placeholders::_1)); + } + catch (::odata::core::odata_exception &e) + { + message.reply(status_codes::BadRequest, U("Exception: ") + e.what()).then(std::bind(&handle_error, std::placeholders::_1)); + } + ////Get odata objects from resorce and odata_path + // + + } + + std::shared_ptr odata_test_service::get_people() + { + auto people_type = m_model->find_entity_type(U("Person")); + auto people_collection_type = std::make_shared(people_type); + auto people_collection = std::make_shared(people_collection_type); + + people_collection->add_collection_value(get_single_people(U("russellwhyte"))); + + people_collection->set_is_top_level(true); + people_collection->set_next_link(U("http://localhost:4789/$metadata#People?$skiptoken=1")); + people_collection->set_delta_link(U("http://localhost:4789/$metadata#People?$deltatoken=0826")); + people_collection->set_count(1); + return people_collection; + } + + std::shared_ptr odata_test_service::get_single_people(::odata::utility::string_t name) + { + if (name != U("russellwhyte")) + { + return std::shared_ptr(); + } + auto people_type = m_model->find_entity_type(U("Person")); + + auto people1 = std::make_shared(people_type); + people1->set_value(U("UserName"), U("russellwhyte")); + people1->set_value(U("FirstName"), U("Russell")); + people1->set_value(U("LastName"),U("Whyte")); + + auto string_collection_type = std::make_shared(::odata::edm::edm_primitive_type::STRING()); + auto emails_value = std::make_shared(string_collection_type); + + emails_value->add_collection_value(std::make_shared(::odata::edm::edm_primitive_type::STRING(), U("Russell@example.com"))); + emails_value->add_collection_value(std::make_shared(::odata::edm::edm_primitive_type::STRING(), U("Russell@contoso.com"))); + people1->set_value(U("Emails"),emails_value); + + auto location_type = m_model->find_complex_type(U("Location")); + auto location_collection_type = std::make_shared( location_type); + auto location_collection = std::make_shared(location_collection_type); + auto location1 = std::make_shared(location_type); + location1->set_value(U("Address"), U("187 Suffolk Ln.")); + + auto city_type = m_model->find_complex_type(U("City")); + auto city1 = std::make_shared(city_type); + city1->set_value(U("CountryRegion"), U("United States")); + city1->set_value(U("Name"), U("Boise")); + city1->set_value(U("Region"), U("ID")); + location1->set_value(U("City"), city1); + location_collection->add_collection_value(location1); + people1->set_value(U("AddressInfo"), location_collection); + + auto gender_type = m_model->find_enum_type(U("PersonGender")); + auto gender = std::make_shared(gender_type, U("Male")); + people1->set_value(U("Gender"),gender); + people1->set_value(U("Concurrency"),635435960069149000L); + + people1->set_etag(U("test etag")); + + + return people1; + } + +}} + +using namespace ::odata::service; + +int _tmain(int argc, _TCHAR* argv[]) +{ + ::odata::utility::string_t directory = argv[0]; + directory.erase(directory.find_last_of('\\')+1); + ::odata::utility::string_t model_file = directory + U("odata_test_service_metadata.xml"); + + std::ifstream ifs(model_file); + auto model_reader = std::make_shared(ifs); + + model_reader->parse(); + auto model = model_reader->get_model(); + + std::shared_ptr service_document = std::make_shared(); + service_document->add_service_document_element(std::make_shared(U("People"), U("People"), ENTITY_SET)); + service_document->add_service_document_element(std::make_shared(U("Customers"), U("Customers"), ENTITY_SET)); + service_document->add_service_document_element(std::make_shared(U("Employees"), U("Employees"), ENTITY_SET)); + service_document->add_service_document_element(std::make_shared(U("Products"), U("Products"), ENTITY_SET)); + service_document->add_service_document_element(std::make_shared(U("Orders"), U("Orders"), ENTITY_SET)); + service_document->add_service_document_element(std::make_shared(U("Company"), U("Company"), SINGLETON)); + service_document->add_service_document_element(std::make_shared(U("GetProductsByAccessLevel"), U("GetProductsByAccessLevel"), FUNCTION_IMPORT)); + + std::wstring address = U("http://localhost:4789"); + + odata_test_service listener(address, model, service_document); + listener.open().wait(); + + std::wcout << ::odata::utility::string_t(U("Listening for requests at: ")) << address << std::endl; + + std::string line; + std::wcout << U("Hit Enter to close the listener."); + std::getline(std::cin, line); + + listener.close().wait(); + + return 0; +} \ No newline at end of file diff --git a/tests/service/odata_test_service.h b/tests/service/odata_test_service.h new file mode 100644 index 0000000..3fdd088 --- /dev/null +++ b/tests/service/odata_test_service.h @@ -0,0 +1,54 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include "cpprest/http_listener.h" +#include "cpprest/json.h" +#include +#include +#include +#include "odata/edm/edm_model_reader.h" +#include "odata/core/odata_uri_parser.h" +#include "odata/core/odata_message_writer.h" +#include "odata/core/odata_select_expand_clause.h" +#include "odata/core/odata_uri.h" +#include "odata/core/odata_collection_value.h" +#include "odata/core/odata_value.h" +#include "odata/core/odata_context_url_builder.h" +#include "odata/core/odata_metadata_builder.h" + +namespace odata { namespace service +{ + +class odata_test_service +{ +public: + odata_test_service(::odata::utility::string_t url, std::shared_ptr<::odata::edm::edm_model> model,std::shared_ptr<::odata::core::odata_service_document> service_document); + + pplx::task open(); + pplx::task close(); + + void initialize_datasource(); + + void handle_get(web::http::http_request message); + +private: + std::shared_ptr<::odata::core::odata_path> process_request_uri(const ::web::uri &relative_uri); + std::shared_ptr<::odata::core::odata_value> get_people(); + std::shared_ptr<::odata::core::odata_entity_value> get_single_people(::odata::utility::string_t name); + + static void handle_error(pplx::task& t); + + // HTTP listener + web::http::experimental::listener::http_listener m_listener; + std::shared_ptr<::odata::core::odata_uri_parser> m_uri_parser; + + std::shared_ptr<::odata::edm::edm_model> m_model; + std::shared_ptr<::odata::core::odata_service_document> m_service_document; + + web::uri m_service_root; +}; + +}} \ No newline at end of file diff --git a/tests/service/odata_test_service_metadata.xml b/tests/service/odata_test_service_metadata.xml new file mode 100644 index 0000000..a00d3a0 --- /dev/null +++ b/tests/service/odata_test_service_metadata.xml @@ -0,0 +1 @@ +image/jpegConcurrencyTripsFriendsapplication/json;odata.metadata=full;IEEE754Compatible=false;odata.streaming=trueapplication/json;odata.metadata=minimal;IEEE754Compatible=false;odata.streaming=trueapplication/json;odata.metadata=none;IEEE754Compatible=false;odata.streaming=truecontainsendswithstartswithlengthindexofsubstringtolowertouppertrimconcatyearmonthdayhourminutesecondroundfloorceilingcastisof \ No newline at end of file diff --git a/tests/service/odata_test_service_mongo.cpp b/tests/service/odata_test_service_mongo.cpp new file mode 100644 index 0000000..bab9638 --- /dev/null +++ b/tests/service/odata_test_service_mongo.cpp @@ -0,0 +1,311 @@ +//--------------------------------------------------------------------- +// +// Copyright (C) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information. +// +//--------------------------------------------------------------------- + +#include +#include + +#include +#include + +#include "odata_test_service.h" + +using namespace std; +using namespace web; +using namespace http; +using namespace utility; +using namespace http::experimental::listener; + +using namespace ::odata::core; + +namespace odata { namespace service +{ + +// TODO replace ::utility::string_t with web::url +odata_test_service::odata_test_service(::utility::string_t url, std::shared_ptr<::odata::edm::edm_model> model, std::shared_ptr service_document) + : m_listener(url), m_model(model), m_service_document(service_document) +{ + m_uri_parser = std::make_shared<::odata::core::odata_uri_parser>(model); + m_service_root = web::uri(url); + m_listener.support(methods::GET, std::bind(&odata_test_service::handle_get, this, std::placeholders::_1)); +} + +void odata_test_service::handle_error(pplx::task& t) +{ + try + { + t.get(); + } + catch(...) + { + // Ignore the error, Log it if a logger is available + } +} + +void odata_test_service::initialize_datasource() +{ + string db_name("trippin"); + + mongo::DBClientConnection c; + c.connect("localhost"); + + c.dropDatabase(db_name); + + string people_collection = db_name + "." + "People"; + vector people_data; + people_data.push_back(BSON("UserName" << "russellwhyte" << "FirstName" << "Russell" << "LastName" << "Whyte" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Russell@example.com" << "Russell@contoso.com") + << "AddressInfo" << BSON_ARRAY(BSON("Address" << "187 Suffolk Ln." << "City" << BSON("CountryRegion" << "United States" << "Name" << "Boise" << "Region" << "ID"))) + << "RecentAirports" << BSON_ARRAY("KSFO" << "KLAX") + )); + people_data.push_back(BSON("UserName" << "scottketchum" << "FirstName" << "Scott" << "LastName" << "Ketchum" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Scott@example.com") + << "AddressInfo" << BSON_ARRAY(BSON("Address" << "2817 Milton Dr." << "City" << BSON("CountryRegion" << "United States" << "Name" << "Albuquerque" << "Region" << "NM"))) + << "RecentAirports" << BSON_ARRAY("KLAX") + )); + people_data.push_back(BSON("UserName" << "ronaldmundy" << "FirstName" << "Ronald" << "LastName" << "Mundy" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Ronald@example.com" << "Ronald@contoso.com") + << "AddressInfo" << mongo::BSONArrayBuilder().arr() + << "RecentAirports" << BSON_ARRAY("ZSSS" << "ZBAA") + )); + people_data.push_back(BSON("UserName" << "javieralfred" << "FirstName" << "Javier" << "LastName" << "Alfred" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Javier@example.com" << "Javier@contoso.com") + << "AddressInfo" << BSON_ARRAY(BSON("Address" << "89 Jefferson Way Suite 2" << "City" << BSON("CountryRegion" << "United States" << "Name" << "Portland" << "Region" << "WA"))) + << "RecentAirports" << mongo::BSONArrayBuilder().arr() + )); + people_data.push_back(BSON("UserName" << "willieashmore" << "FirstName" << "Willie" << "LastName" << "Ashmore" << "Gender" << "Male" + << "Emails" << BSON_ARRAY("Willie@example.com" << "Willie@contoso.com") + << "AddressInfo" << mongo::BSONArrayBuilder().arr() + << "RecentAirports" << mongo::BSONArrayBuilder().arr() + )); + + string airports_collection = db_name + "." + "Airports"; + vector airports_data; + airports_data.push_back(BSON("IcaoCode" << "KSFO" << "Name" << "San Francisco International Airport" << "IataCode" << "SFO" + << "Location" << BSON("Address" << "South McDonnell Road, San Francisco, CA 94128" + << "City" << BSON("CountryRegion" << "United States" << "Name" << "San Francisco" << "Region" << "California")) + )); + airports_data.push_back(BSON("IcaoCode" << "KLAX" << "Name" << "Los Angeles International Airport" << "IataCode" << "LAX" + << "Location" << BSON("Address" << "1 World Way, Los Angeles, CA, 90045" + << "City" << BSON("CountryRegion" << "United States" << "Name" << "Los Angeles" << "Region" << "California")) + )); + airports_data.push_back(BSON("IcaoCode" << "ZSSS" << "Name" << "Shanghai Hongqiao International Airport" << "IataCode" << "SHA" + << "Location" << BSON("Address" << "Hongqiao Road 2550, Changning District" + << "City" << BSON("CountryRegion" << "China" << "Name" << "Shanghai" << "Region" << "Shanghai")) + )); + airports_data.push_back(BSON("IcaoCode" << "ZBAA" << "Name" << "Beijing Capital International Airport" << "IataCode" << "PEK" + << "Location" << BSON("Address" << "Airport Road, Chaoyang District, Beijing, 100621" + << "City" << BSON("CountryRegion" << "China" << "Name" << "Beijing" << "Region" << "Beijing")) + )); + + c.insert(people_collection, people_data); + c.insert(airports_collection, airports_data); +} + +pplx::task odata_test_service::open() +{ + return m_listener.open().then(std::bind(&handle_error, std::placeholders::_1)); +} + +pplx::task odata_test_service::close() +{ + return m_listener.close().then(std::bind(&handle_error, std::placeholders::_1)); +} + +// Handler to process HTTP::GET requests. +// Replies to the request with data. +void odata_test_service::handle_get(http_request message) +{ + try + { + auto parsed_uri = m_uri_parser->parse_uri(message.relative_uri()); + auto path_segments = parsed_uri->path()->segments(); + + string db_name("trippin"); + string collection_name; + mongo::BSONObjBuilder query_builder; + mongo::BSONObjBuilder projection_builder; + + for (auto iter = path_segments.cbegin(); iter != path_segments.cend(); iter++) + { + shared_ptr seg = *iter; + switch (seg->segment_type()) + { + case odata_path_segment_type::EntitySet: + { + shared_ptr entityset_seg = seg->as(); + auto entityset_name = entityset_seg->entity_set()->get_name(); + + collection_name.assign(entityset_name.cbegin(), entityset_name.cend()); + break; + } + + case odata_path_segment_type::Key: + { + shared_ptr key_seg = seg->as(); + auto keys = key_seg->keys(); + + for (auto key = keys.cbegin(); key != keys.cend(); key++) + { + string key_name_s, key_value_s; + key_name_s.assign(key->first.cbegin(), key->first.cend()); + auto second = key->second->as(); + key_value_s.assign(second.cbegin(), second.cend()); + query_builder << key_name_s << key_value_s; + } + break; + } + + case odata_path_segment_type::StructuralProperty: + { + shared_ptr property_seg = seg->as(); + string_t property_name = property_seg->property()->get_name(); + + string property_name_s; + property_name_s.assign(property_name.cbegin(), property_name.cend()); + projection_builder << property_name_s << 1; + + break; + } + + default: + + break; + } + + } + + mongo::DBClientConnection c; + c.connect("localhost"); + + collection_name = db_name + "." + collection_name; + auto cursor = c.query(collection_name, query_builder.obj(), 0, 0, &projection_builder.obj()); + + ::utility::string_t content; + + string retrived_data; + while (cursor->more()) + { + auto next = cursor->next(); + next.removeField("_id"); + retrived_data += next.toString(); + } + content = conversions::to_string_t(retrived_data); + + odata_message_writer writer(m_model, m_service_root); + + if (parsed_uri->is_service_document()) + { + // Write service document + content = writer.write_service_document(m_service_document); + } + else if (parsed_uri->is_metadata_document()) + { + // Write metadata document + content = writer.write_metadata_document(); + } + + message.reply(status_codes::OK, content).then(std::bind(&handle_error, std::placeholders::_1)); + } + catch (::odata::core::odata_exception &e) + { + message.reply(status_codes::OK, U("Exception: ") + e.what()).then(std::bind(&handle_error, std::placeholders::_1)); + } + ////Get odata objects from resorce and odata_path + // + //odata_message_writer writer(model); + //if (it is feed) + // odata_feed_writer feed_writer = writer.create_feed_writer(); + // feed_writer.write_start(feed); + // feed_writer.write_start(entry); + // feed_writer.write_end(); + // feed_writer.write_end(); + //else if (it is entry) + // odata_entry_writer entry_writer = writer.create_entry_writer(); + // entry_writer.write_start(entry); + // entry_writer.write_end(); +} + +}} + +using namespace ::odata::service; + +int main(int argc, char* argv[]) +{ + +const char* test_edm_model = +" \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ + \ +"; + + auto model_reader = std::make_shared<::odata::edm::edm_model_reader>(concurrency::streams::bytestream::open_istream(std::string(test_edm_model))); + model_reader->parse(); + auto model = model_reader->get_model(); + + std::shared_ptr service_document = std::make_shared(); + service_document->add_service_document_element(std::make_shared(U("People"), U("People"), ENTITY_SET)); + service_document->add_service_document_element(std::make_shared(U("Airports"), U("Airports"), ENTITY_SET)); + + std::wstring address = U("http://localhost:4789"); + + odata_test_service listener(address, model, service_document); + listener.open().wait(); + + std::wcout << ::utility::string_t(U("Listening for requests at: ")) << address << std::endl; + + std::string line; + std::wcout << U("Hit Enter to close the listener."); + std::getline(std::cin, line); + + listener.close().wait(); + + return 0; +} \ No newline at end of file