message:
Upgrade to CXX 17
Fix TestInstall build
Fix few issues surfaced during regression tests

diff:
@@ -30,7 +30,7 @@ add_subdirectory(third_party/antlr4/runtime/Cpp EXCLUDE_FROM_ALL)
add_subdirectory(third_party/UHDM)
add_subdirectory(third_party/googletest EXCLUDE_FROM_ALL)
-set(CMAKE_CXX_STANDARD 14)
+set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

diff:
@@ -113,7 +113,8 @@ int FileUtils::mkDir(const char* path) {
}
std::string FileUtils::getPathName(const std::string path) {
- return (fs::path(path).parent_path() += fs::path::preferred_separator).string();
+ fs::path fs_path(path);
+ return fs_path.has_parent_path() ? (fs::path(path).parent_path() += fs::path::preferred_separator).string() : "";
}
std::string FileUtils::getFullPath(const std::string path) {
@@ -125,11 +126,11 @@ std::string FileUtils::getFullPath(const std::string path) {
bool FileUtils::getFullPath(const std::string path, std::string* const result) {
std::error_code ec;
fs::path fullPath = fs::canonical(path, ec);
- if (!ec && fileIsRegular(fullPath.string())) {
- if (result != nullptr) *result = fullPath.string();
- return true;
+ bool found = (!ec && fileIsRegular(fullPath.string()));
+ if (result != nullptr) {
+ *result = found ? fullPath.string() : path;
}
- return false;
+ return found;
}
static bool has_suffix(const std::string& s, const std::string& suffix) {
@@ -186,7 +187,7 @@ std::vector<SymbolId> FileUtils::collectFilesRegexp(const std::string dirPath,
std::smatch base_match;
for (fs::directory_entry entry : fs::directory_iterator(dirPath)) {
- std::string value = entry.path().string();
+ std::string value = entry.path().filename().string();
if (std::regex_match(value, base_match, base_regex)) {
result.push_back(symbols->registerSymbol(value));
}
@@ -281,11 +282,6 @@ std::string FileUtils::getFileContent(const std::string filename) {
}
std::string FileUtils::fileName(std::string str) {
- char c = '/';
- auto it1 = std::find_if(str.rbegin(), str.rend(),
- [c](char ch) { return (ch == c); });
- if (it1 != str.rend())
- str.erase(str.begin(), it1.base());
- return str;
+ return fs::path(str).filename().string();
}

diff:
# -*- mode:cmake -*-
+cmake_minimum_required(VERSION 3.13)
# Usage:
# After a make install at the top drectory:
project(HELLOSUREWORLD)
-set(CMAKE_CXX_STANDARD 14)
-
-cmake_minimum_required(VERSION 3.13)
+set(CMAKE_CXX_STANDARD 17)
# Python
find_package(Python3 3.3 REQUIRED COMPONENTS Interpreter Development)


message:
Continuing verification on Windows
* Move path manipulation functions out of StringUtils into FileUtils
* Don't assume forward/back slash when dealing with paths. Handle both.
* Reimplement (and renamed StringUtils::eliminateRelativePath) to be
more specific of its purpose. Logic now handles both relative and
absolute paths on both unix and windows platform.

change_type: MODIFY

diff:
@@ -54,9 +54,8 @@ std::string PPCache::getCacheFileName_(std::string svFileName) {
m_pp->getCompileSourceFile()->getCommandLineParser()->getCacheDir();
if (svFileName == "") svFileName = m_pp->getFileName(LINE1);
- std::string root = svFileName;
- root = StringUtils::getRootFileName(root);
- if (prec->isFilePrecompiled(root)) {
+ svFileName = FileUtils::fileName(svFileName);
+ if (prec->isFilePrecompiled(svFileName)) {
std::string packageRepDir = m_pp->getSymbol(m_pp->getCompileSourceFile()
->getCommandLineParser()
->getPrecompiledDir());
@@ -71,7 +70,6 @@ std::string PPCache::getCacheFileName_(std::string svFileName) {
Library* lib = m_pp->getLibrary();
std::string libName = lib->getName() + "/";
- svFileName = StringUtils::getRootFileName(svFileName);
std::string cacheFileName = cacheDirName + libName + svFileName + ".slpp";
FileUtils::mkDir(std::string(cacheDirName + libName).c_str());
return cacheFileName;

diff:
@@ -60,9 +60,8 @@ std::string ParseCache::getCacheFileName_(std::string svFileName) {
SymbolId cacheDirId =
m_parse->getCompileSourceFile()->getCommandLineParser()->getCacheDir();
if (svFileName == "") svFileName = m_parse->getPpFileName();
- std::string root = svFileName;
- root = StringUtils::getRootFileName(root);
- if (prec->isFilePrecompiled(root)) {
+ svFileName = FileUtils::fileName(svFileName);
+ if (prec->isFilePrecompiled(svFileName)) {
std::string packageRepDir =
m_parse->getSymbol(m_parse->getCompileSourceFile()
->getCommandLineParser()
@@ -77,7 +76,6 @@ std::string ParseCache::getCacheFileName_(std::string svFileName) {
std::string cacheDirName = m_parse->getSymbol(cacheDirId);
Library* lib = m_parse->getLibrary();
std::string libName = lib->getName() + "/";
- svFileName = StringUtils::getRootFileName(svFileName);
std::string cacheFileName = cacheDirName + libName + svFileName + ".slpa";
FileUtils::mkDir(std::string(cacheDirName + libName).c_str());
return cacheFileName;

diff:
@@ -61,7 +61,7 @@ std::string PythonAPICache::getCacheFileName_(std::string svFileName) {
std::string cacheDirName = m_listener->getParseFile()->getSymbol(cacheDirId);
if (svFileName == "")
svFileName = m_listener->getParseFile()->getFileName(LINE1);
- svFileName = StringUtils::getRootFileName(svFileName);
+ svFileName = FileUtils::fileName(svFileName);
Library* lib = m_listener->getCompileSourceFile()->getLibrary();
std::string libName = lib->getName() + "/";
std::string cacheFileName = cacheDirName + libName + svFileName + ".slpy";

diff:
#include <sstream>
#include <cstdlib>
#include <string.h>
-#include <sys/param.h>
#include <sys/stat.h>
#include <sys/types.h>
+
+#if (defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__))
+ #include <direct.h>
+ #define PATH_MAX _MAX_PATH
+#else
+ #include <sys/param.h>
#include <unistd.h>
+#endif
#include <fstream>
#include <iostream>

diff:
@@ -218,7 +218,7 @@ bool CompileSourceFile::parse_() {
bool CompileSourceFile::preprocess_() {
Precompiled* prec = Precompiled::getSingleton();
std::string root = getSymbolTable()->getSymbol(m_fileId);
- root = StringUtils::getRootFileName(root);
+ root = FileUtils::fileName(root);
PreprocessFile::SpecialInstructions instructions(
PreprocessFile::SpecialInstructions::DontMute,
@@ -261,17 +261,14 @@ bool CompileSourceFile::postPreprocess_() {
(m_commandLineParser->writePpOutputFileId() != 0)) {
const std::string& directory =
symbolTable->getSymbol(m_commandLineParser->getFullCompileDir());
- std::string fileName = symbolTable->getSymbol(m_fileId);
- std::string fullPath = FileUtils::getFullPath(fileName);
- fileName = StringUtils::eliminateRelativePath(fileName);
+ std::string fileName = FileUtils::makeRelativePath(symbolTable->getSymbol(m_fileId));
const std::string& writePpOutputFileName =
symbolTable->getSymbol(m_commandLineParser->writePpOutputFileId());
std::string libName = m_library->getName() + "/";
string ppFileName = m_commandLineParser->writePpOutput()
? directory + libName + fileName
: writePpOutputFileName;
- std::string dirPpFile = ppFileName;
- StringUtils::rtrim(dirPpFile, '/');
+ std::string dirPpFile = FileUtils::getPathName(ppFileName);
SymbolId ppOutId = symbolTable->registerSymbol(ppFileName);
m_ppResultFileId = m_symbolTable->registerSymbol(ppFileName);
SymbolId ppDirId = symbolTable->registerSymbol(dirPpFile);

diff:
@@ -252,7 +252,7 @@ bool Compiler::createFileList_()
for (unsigned int i = 0; i < size; i++) {
std::string fileName = m_compilers[i]->getSymbolTable()->getSymbol(
m_compilers[i]->getPpOutputFileId());
- fileName = StringUtils::replaceAll(fileName, "//", "/");
+ fileName = FileUtils::getPreferredPath(fileName);
ofs << fileName << std::flush << std::endl;
}
ofs.close();
@@ -299,7 +299,7 @@ bool Compiler::createMultiProcess_() {
Precompiled* prec = Precompiled::getSingleton();
for (unsigned int i = 0; i < m_compilers.size(); i++) {
std::string root = m_compilers[i]->getSymbolTable()->getSymbol(m_compilers[i]->getFileId());
- root = StringUtils::getRootFileName(root);
+ root = FileUtils::fileName(root);
if (prec->isFilePrecompiled(root)) {
continue;
}
@@ -417,8 +417,7 @@ bool Compiler::parseinit_() {
std::string origFile = m_compilers[i]->getSymbolTable()->getSymbol(
m_compilers[i]->getFileId());
unsigned int nbThreads = m_commandLineParser->getNbMaxTreads();
- std::string root = fileName;
- root = StringUtils::getRootFileName(root);
+ std::string root = FileUtils::fileName(fileName);
if (prec->isFilePrecompiled(root)) {
nbThreads = 0;
}

diff:
@@ -343,8 +343,7 @@ std::string ParseFile::getProfileInfo() {
bool ParseFile::parse() {
Precompiled* prec = Precompiled::getSingleton();
- std::string root = this->getPpFileName();
- root = StringUtils::getRootFileName(root);
+ std::string root = FileUtils::fileName(this->getPpFileName());
bool precompiled = false;
if (prec->isFilePrecompiled(root)) precompiled = true;

diff:
@@ -296,8 +296,7 @@ bool PreprocessFile::preprocess() {
m_result = "";
std::string fileName = getSymbol(m_fileId);
Precompiled* prec = Precompiled::getSingleton();
- std::string root = fileName;
- root = StringUtils::getRootFileName(root);
+ std::string root = FileUtils::fileName(fileName);
bool precompiled = false;
if (prec->isFilePrecompiled(root)) precompiled = true;

diff:
#if (defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__))
#include <direct.h>
#define PATH_MAX _MAX_PATH
- // Supress the warning for the time being until we upgrade to C++17
- #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#else
#include <dirent.h>
#include <unistd.h>
@@ -64,15 +62,13 @@ FileUtils::FileUtils(const FileUtils& orig) {}
FileUtils::~FileUtils() {}
bool FileUtils::fileExists(const std::string name) {
- struct stat buffer;
- return (stat(name.c_str(), &buffer) == 0);
+ std::error_code ec;
+ return fs::exists(name, ec);
}
unsigned long FileUtils::fileSize(const std::string name) {
- struct stat buf;
- stat(name.c_str(), &buf);
- off_t size = buf.st_size;
- return size;
+ std::error_code ec;
+ return fs::file_size(name, ec);
}
bool FileUtils::fileIsDirectory(const std::string name) {
@@ -285,3 +281,27 @@ std::string FileUtils::fileName(std::string str) {
return fs::path(str).filename().string();
}
+std::string FileUtils::getPreferredPath(const std::string& path) {
+ return fs::path(path).make_preferred().string();
+}
+
+std::string FileUtils::makeRelativePath(std::string path) {
+ // Replace everything that's not alpha-numeric with an '_'
+ const std::string allowed_in_dir = "-/\\";
+ const std::string allowed_in_file = ".";
+ std::string dir = FileUtils::getPathName(path);
+ std::string file = FileUtils::fileName(path);
+ std::replace_if(
+ dir.begin(), dir.end(),
+ [&allowed_in_dir](std::string::value_type ch) {
+ return !std::isalnum(ch) && (allowed_in_dir.find(ch) == std::string::npos);
+ },
+ '_');
+ std::replace_if(
+ file.begin(), file.end(),
+ [&allowed_in_file](std::string::value_type ch) {
+ return !std::isalnum(ch) && (allowed_in_file.find(ch) == std::string::npos);
+ },
+ '_');
+ return (fs::path(dir) / file).string();
+}

diff:
@@ -60,6 +60,8 @@ class FileUtils {
static std::vector<SymbolId> collectFiles(std::string pathSpec,
SymbolTable* symbols);
static std::string getFileContent(const std::string name);
+ static std::string getPreferredPath(const std::string &path);
+ static std::string makeRelativePath(std::string path);
private:
FileUtils();

diff:
@@ -275,14 +275,6 @@ std::string StringUtils::leaf(std::string str) {
return str;
}
-std::string& StringUtils::getRootFileName(std::string& str) {
- bool found = true;
- while (found) {
- found = ltrimStat(str, '/');
- }
- return str;
-}
-
std::string StringUtils::replaceAll(std::string str, const std::string& from,
const std::string& to) {
size_t start_pos = 0;
@@ -294,10 +286,6 @@ std::string StringUtils::replaceAll(std::string str, const std::string& from,
return str;
}
-std::string StringUtils::eliminateRelativePath(std::string path) {
- return replaceAll(path, "..", "__");
-}
-
std::string StringUtils::getLineInString(std::string& bulk, unsigned int line) {
std::string lineText;
unsigned int size = bulk.size();

diff:
@@ -52,8 +52,6 @@ class StringUtils {
static std::string& rtrim(std::string& str, char c);
static std::string leaf(std::string str);
- static std::string& getRootFileName(std::string& str);
- static std::string eliminateRelativePath(std::string path);
static std::string replaceAll(std::string str, const std::string& from,
const std::string& to);
static std::string getLineInString(std::string& bulk, unsigned int line);

diff:
#include <string>
#include <vector>
#include <sys/stat.h>
+#include <fstream>
+
+#if (defined(_MSC_VER) || defined(__MINGW32__) || defined(__CYGWIN__))
+ #include <direct.h>
+ #include <process.h>
+#else
#include <sys/param.h>
#include <unistd.h>
-#include <fstream>
+#endif
#include "surelog.h"
#include "ErrorReporting/Report.h"
@@ -225,8 +231,8 @@ int main(int argc, const char ** argv) {
printf("fork() failed!\n");
return 1;
}
- break;
#endif
+ break;
}
case NORMAL:
codedReturn = executeCompilation(argc, argv, false, false);

license: Apache License 2.0

repo: chipsalliance/surelog


message:
Issue Mangled Paths
Paths generated by FileUtils::makeRelativePath didn't account for
wildcards in filenames. Reverting it back to version that worked
with smaller change to handle paths on different platforms.

diff:
@@ -286,22 +286,12 @@ std::string FileUtils::getPreferredPath(const std::string& path) {
}
std::string FileUtils::makeRelativePath(std::string path) {
- // Replace everything that's not alpha-numeric with an '_'
- const std::string allowed_in_dir = "-/\\";
- const std::string allowed_in_file = ".";
- std::string dir = FileUtils::getPathName(path);
- std::string file = FileUtils::fileName(path);
- std::replace_if(
- dir.begin(), dir.end(),
- [&allowed_in_dir](std::string::value_type ch) {
- return !std::isalnum(ch) && (allowed_in_dir.find(ch) == std::string::npos);
- },
- '_');
- std::replace_if(
- file.begin(), file.end(),
- [&allowed_in_file](std::string::value_type ch) {
- return !std::isalnum(ch) && (allowed_in_file.find(ch) == std::string::npos);
- },
- '_');
- return (fs::path(dir) / file).string();
+ const std::string separator(1, fs::path::preferred_separator);
+ // Standardize it so we can avoid special cases and wildcards!
+ path = fs::path(path).make_preferred().string();
+ // Swap "\.\" (or "/./") for "\" (or "/")
+ path = StringUtils::replaceAll(path, separator + "." + separator, separator);
+ // Swap "\..\" (or "/../") for "\__\" (or "/__/")
+ path = StringUtils::replaceAll(path, separator + ".." + separator, "__");
+ return path;
}

original_message:
Issue #446: Mangled Paths

Paths generated by FileUtils::makeRelativePath didn't account for
wildcards in filenames. Reverting it back to version that worked
with smaller change to handle paths on different platforms.


message:
Remove duplicate definition of SV3_1aPpTreeShapeListener::enterInclude_directive
in file.

diff:
@@ -1187,9 +1187,6 @@ void SV3_1aPpTreeShapeListener::exitDefault_decay_time_directive(SV3_1aPpParser:
m_pp->resumeAppend();
}
-void SV3_1aPpTreeShapeListener::enterInclude_directive(SV3_1aPpParser::Include_directiveContext * ctx);
-void SV3_1aPpTreeShapeListener::exitInclude_directive(SV3_1aPpParser::Include_directiveContext * ctx);
-
void SV3_1aPpTreeShapeListener::enterUnconnected_drive_directive(SV3_1aPpParser::Unconnected_drive_directiveContext *ctx)
{
forwardToParser(ctx);


message:
As per Microsoft dev blog here -
VC compiler reports wrong value for macro __cplusplus and need a "special"
compiler flag to fix it. Updating make file accordingly.

diff:
@@ -81,11 +81,15 @@ set(CMAKE_CXX_FLAGS
"${CMAKE_CXX_FLAGS} ${TCMALLOC_COMPILE_OPTIONS} ${MY_CXX_WARNING_FLAGS}")
if(MSVC)
+ add_compile_definitions(PLI_DLLISPEC=)
+ add_compile_definitions(ANTLR4CPP_STATIC)
+ add_compile_definitions(_CRT_NONSTDC_NO_WARNINGS)
+
set(CMAKE_CXX_FLAGS_DEBUG
- "${CMAKE_CXX_FLAGS_DEBUG} ${TCMALLOC_COMPILE_OPTIONS} /W0 /bigobj /DPLI_DLLISPEC= /DANTLR4CPP_STATIC /D_CRT_NONSTDC_NO_WARNINGS ${MY_CXX_WARNING_FLAGS}"
+ "${CMAKE_CXX_FLAGS_DEBUG} ${TCMALLOC_COMPILE_OPTIONS} /Zc:__cplusplus /W0 /bigobj ${MY_CXX_WARNING_FLAGS}"
)
set(CMAKE_CXX_FLAGS_RELEASE
- "${CMAKE_CXX_FLAGS_RELEASE} ${TCMALLOC_COMPILE_OPTIONS}/W0 /bigobj /DPLI_DLLISPEC= /DANTLR4CPP_STATIC /D_CRT_NONSTDC_NO_WARNINGS ${MY_CXX_WARNING_FLAGS}"
+ "${CMAKE_CXX_FLAGS_RELEASE} ${TCMALLOC_COMPILE_OPTIONS} /Zc:__cplusplus /W0 /bigobj ${MY_CXX_WARNING_FLAGS}"
)
set(CMAKE_EXE_LINKER_FLAGS /STACK:8388608) # 8MB stack size
else()

original_message:
As per Microsoft dev blog here -
https://devblogs.microsoft.com/cppblog/msvc-now-correctly-reports-__cplusplus/
VC compiler reports wrong value for macro __cplusplus and need a "special"
compiler flag to fix it. Updating make file accordingly.


message:
Issue
Get TestInstall to work on Windows

diff:
# -*- mode:cmake -*-
-cmake_minimum_required(VERSION 3.13)
+cmake_minimum_required(VERSION 3.15)
# Usage:
# After a make install at the top drectory:
@@ -13,7 +13,13 @@ cmake_minimum_required(VERSION 3.13)
project(HELLOSUREWORLD)
+# NOTE: Policy changes has to happen before adding any subprojects
+cmake_policy(SET CMP0091 NEW)
+set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
+
set(CMAKE_CXX_STANDARD 17)
+set(CMAKE_CXX_STANDARD_REQUIRED ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# Python
find_package(Python3 3.3 REQUIRED COMPONENTS Interpreter Development)
@@ -34,8 +40,13 @@ if(NOT NO_TCMALLOC)
endif(NOT NO_TCMALLOC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${TCMALLOC_COMPILE_OPTIONS} ${MY_CXX_WARNING_FLAGS}")
+if(MSVC)
+ set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${TCMALLOC_COMPILE_OPTIONS} /Zc:__cplusplus /W0 /bigobj ${MY_CXX_WARNING_FLAGS}")
+ set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${TCMALLOC_COMPILE_OPTIONS} /Zc:__cplusplus /W0 /bigobj ${MY_CXX_WARNING_FLAGS}")
+else()
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} ${TCMALLOC_COMPILE_OPTIONS} -Wall -O0 -g ${MY_CXX_WARNING_FLAGS}")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} ${TCMALLOC_COMPILE_OPTIONS} -Wall -O3 -DNDEBUG ${MY_CXX_WARNING_FLAGS}")
+endif()
set (INCLUDE_DIR "${INSTALL_DIR}/include/surelog/")
@@ -49,16 +60,31 @@ if(NOT NO_TCMALLOC)
set (TCMALLOC_LIBRARY tcmalloc)
endif(NOT NO_TCMALLOC)
-set (INSTALL_LIBRARIES_FOR_SURELOG
- -L${LIB_DIR}surelog/ surelog
- antlr4-runtime
+add_executable(test_hellosureworld ${PROJECT_SOURCE_DIR}/../../src/hellosureworld.cpp)
+
+target_link_directories(test_hellosureworld
+ PRIVATE ${LIB_DIR}surelog/
+ PRIVATE ${LIB_DIR}uhdm/
+)
+
+target_link_libraries(test_hellosureworld
+ surelog
+ antlr4-runtime$<$<CXX_COMPILER_ID:MSVC>:-static>
flatbuffers
uhdm
- -L${LIB_DIR}uhdm/
- capnp kj
- ${Python3_LIBRARIES} dl util m rt pthread ${TCMALLOC_LIBRARY}
+ capnp
+ kj
+ ${Python3_LIBRARIES}
+ ${TCMALLOC_LIBRARY}
)
-add_executable(test_hellosureworld ${PROJECT_SOURCE_DIR}/../../src/hellosureworld.cpp)
-target_link_libraries(test_hellosureworld ${INSTALL_LIBRARIES_FOR_SURELOG})
-target_link_libraries(test_hellosureworld stdc++fs)
+if (UNIX)
+ target_link_libraries(test_hellosureworld
+ stdc++fs
+ dl
+ util
+ m
+ rt
+ pthread
+ )
+endif()
\ No newline at end of file

original_message:
Issue #239
Get TestInstall to work on Windows


message:
Issue
CMake builds create symlink within a symlink
Cause
As per the unix docs for 'ln' command -
When the last argument to ln is a directory, the links are
made in that directory.
Resolution
Don't add the directory at the end of the command.

diff:
@@ -392,9 +392,9 @@ else()
TARGET surelog-bin
POST_BUILD
COMMAND echo " Creating platform depdendent staging for precompiled packages"
- COMMAND ln -fs ${PROJECT_SOURCE_DIR}/third_party/UVM/ovm-2.1.2 ovm-2.1.2
- COMMAND ln -fs ${PROJECT_SOURCE_DIR}/third_party/UVM/1800.2-2017-1.0 1800.2-2017-1.0
- COMMAND ln -fs ${PROJECT_SOURCE_DIR}/third_party/UVM/vmm-1.1.1a vmm-1.1.1a
+ COMMAND ln -fs ${PROJECT_SOURCE_DIR}/third_party/UVM/ovm-2.1.2
+ COMMAND ln -fs ${PROJECT_SOURCE_DIR}/third_party/UVM/1800.2-2017-1.0
+ COMMAND ln -fs ${PROJECT_SOURCE_DIR}/third_party/UVM/vmm-1.1.1a
COMMAND echo " Platform depdendent staging completed"
WORKING_DIRECTORY "${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")
endif()


message:
Issue
Implementation of FileUtils::collectFiles isn't thread safe.
Cause
The use of 'chdir' makes the implementation non-thread-safe.
Resolution
Reimplemented the function to work with filesystem and strings
only i.e. without any OS calls (except calls to scan for files)

diff:
@@ -155,114 +155,76 @@ std::vector<SymbolId> FileUtils::collectFiles(const std::string dirPath,
return result;
}
-std::vector<SymbolId> FileUtils::collectFilesRegexp(const std::string dirPath,
- const std::string regexp,
- SymbolTable* symbols) {
- std::vector<SymbolId> result;
- if (!fileIsDirectory(dirPath)) {
- return result;
- }
-
- std::string newregexp;
- for (unsigned int i = 0; i < regexp.size(); i++) {
- if (regexp[i] == '.') {
- newregexp += "\\";
- }
- if (regexp[i] == '*') {
- newregexp += "[a-zA-Z0-9_-]*\\.*[a-zA-Z0-9_]*";
- continue;
- }
- if (regexp[i] == '?') {
- newregexp += "[a-zA-Z0-9_-]+";
- continue;
- }
- newregexp += regexp[i];
- }
-
- std::regex base_regex(newregexp);
- std::smatch base_match;
-
- for (fs::directory_entry entry : fs::directory_iterator(dirPath)) {
- std::string value = entry.path().filename().string();
- if (std::regex_match(value, base_match, base_regex)) {
- result.push_back(symbols->registerSymbol(value));
- }
- }
-
- return result;
-}
+std::vector<SymbolId> FileUtils::collectFiles(const std::string& pathSpec,
+ SymbolTable* const symbols) {
+ // ? single character wildcard (matches any single character)
+ // * multiple character wildcard (matches any number of characters in a
+ // directory/file name)
+ // ... hierarchical wildcard (matches any number of hierarchical directories)
+ // .. specifies the parent directory
+ // . specifies the directory containing the lib.map
+ // Paths that end in / shall include all files in the specified directory.
+ // Identical to / * Paths that do not begin with / are relative to the directory
+ // in which the current lib.map file is located.
-void FileUtils::collectFiles(const std::string dirPath,
- std::vector<std::string>& dirs, unsigned int level,
- SymbolTable* symbols,
- std::vector<SymbolId>& files) {
- char currentDir[4096];
- if (!getcwd(currentDir, 4096)) return;
-
- if (chdir(dirPath.c_str()) != 0) return;
- char dir[4096];
- if (!getcwd(dir, 4096)) return;
-
- std::string regexp = dirs[level];
- std::vector<SymbolId> ids = collectFilesRegexp("./", regexp, symbols);
- for (auto id : ids) {
- std::string file = "./" + symbols->getSymbol(id);
- if (fileIsDirectory(file)) {
- if ((level + 1) < dirs.size()) {
- collectFiles(file, dirs, level + 1, symbols, files);
- }
- } else {
- std::string filePath = dir + std::string("/") + symbols->getSymbol(id);
- files.push_back(symbols->registerSymbol(filePath));
- }
- }
-
- if (chdir(currentDir) != 0) return;
-}
-
-std::vector<SymbolId> FileUtils::collectFiles(std::string pathSpec,
- SymbolTable* symbols) {
std::vector<SymbolId> result;
- char currentDir[4096];
- if (!getcwd(currentDir, 4096)) return result;
- /*
- ? single character wildcard (matches any single character)
- * multiple character wildcard (matches any number of characters in a
- directory/file name)
- ... hierarchical wildcard (matches any number of hierarchical directories)
- .. specifies the parent directory
- . specifies the directory containing the lib.map
- Paths that end in / shall include all files in the specified directory.
- Identical to / * Paths that do not begin with / are relative to the directory
- in which the current lib.map file is located.
- */
- if (pathSpec[pathSpec.size() - 1] == '/') {
- pathSpec += "*";
+ std::error_code ec;
+ fs::path path(pathSpec);
+ if (!path.is_absolute()) {
+ path = fs::current_path(ec) / path;
+ if (ec) return result;
+ }
+ path.make_preferred();
+
+ fs::path prefix;
+ fs::path suffix;
+ for (const fs::path &subpath : path)
+ {
+ const std::string substr = subpath.string();
+ if (substr.compare(".") == 0)
+ continue;
+ else if (!suffix.empty())
+ suffix /= subpath;
+ else if (substr.find_first_of(".?*") == std::string::npos)
+ prefix /= subpath;
+ else
+ suffix /= subpath;
}
- std::vector<std::string> dirs;
- StringUtils::tokenize(pathSpec, "/", dirs);
+ prefix = fs::canonical(prefix, ec);
+ if (ec) return result;
+ if (suffix.empty()) suffix /= "*";
- if (pathSpec[0] == '/') {
- // Absolute path
- if (chdir("/") != 0) return result;
+ const std::string separator(1, fs::path::preferred_separator);
+ const std::string escaped = "\\" + separator;
+ std::string regexp = suffix.string();
+ regexp = StringUtils::replaceAll(regexp, separator, escaped); // escape separators
+ regexp = StringUtils::replaceAll(regexp, "..." + escaped,
+ R"([a-zA-Z0-9_\-.)" + escaped + R"(]+)" + escaped); // separator allowed
+ regexp = StringUtils::replaceAll(regexp, ".." + escaped,
+ R"([a-zA-Z0-9_\-.]+)" + escaped + R"([a-zA-Z0-9_\-.]+)" + escaped); // separator NOT allowed
+ regexp = StringUtils::replaceAll(regexp, ".", "\\."); // escape it
+ regexp = StringUtils::replaceAll(regexp, "?", R"([a-zA-Z0-9_\-\.])"); // at most one
+ regexp = StringUtils::replaceAll(regexp, "*", ".*"); // free for all
+
+ const std::regex regex(regexp);
+ const fs::directory_options options =
+ fs::directory_options::skip_permission_denied |
+ fs::directory_options::follow_directory_symlink;
+
+ for (fs::directory_entry entry :
+ fs::recursive_directory_iterator(prefix, options)) {
+ if (fs::is_regular_file(entry.path())) {
+ const std::string relative =
+ entry.path().string().substr(prefix.string().length() + 1);
+ std::smatch match;
+ if (!ec && std::regex_match(relative, match, regex)) {
+ result.push_back(symbols->registerSymbol(entry.path().string()));
}
-
- char dir[4096];
- if (!getcwd(dir, 4096)) return result;
- std::vector<SymbolId> subs = collectFilesRegexp("./", dirs[0], symbols);
- for (auto id : subs) {
- std::string file = "./" + symbols->getSymbol(id);
- if (fileIsDirectory(file))
- collectFiles(file, dirs, 1, symbols, result);
- else {
- std::string filePath = dir + std::string("/") + symbols->getSymbol(id);
- result.push_back(symbols->registerSymbol(filePath));
}
}
- if (chdir(currentDir) != 0) return result;
return result;
}
@@ -288,10 +250,17 @@ std::string FileUtils::getPreferredPath(const std::string& path) {
std::string FileUtils::makeRelativePath(std::string path) {
const std::string separator(1, fs::path::preferred_separator);
// Standardize it so we can avoid special cases and wildcards!
- path = fs::path(path).make_preferred().string();
+ fs::path p(path);
+ path = p.make_preferred().string();
+ // Handle Windows specific absolute paths
+ if (p.is_absolute() && (path.length() > 1) && (path[1] == ':')) path[1] = '$';
+ // Swap "..\" (or "../") for "__\" (or "__/")
+ path = StringUtils::replaceAll(path, ".." + separator, "__" + separator);
// Swap "\.\" (or "/./") for "\" (or "/")
path = StringUtils::replaceAll(path, separator + "." + separator, separator);
- // Swap "\..\" (or "/../") for "\__\" (or "/__/")
- path = StringUtils::replaceAll(path, separator + ".." + separator, "__");
+ if (path[0] != '.') {
+ if (path[0] != fs::path::preferred_separator) path = separator + path;
+ path = "." + path;
+ }
return path;
}

diff:
@@ -51,14 +51,8 @@ class FileUtils {
static std::vector<SymbolId> collectFiles(SymbolId dirPath,
SymbolId extension,
SymbolTable* symbols);
- static std::vector<SymbolId> collectFilesRegexp(const std::string dirPath,
- const std::string regexp,
- SymbolTable* symbols);
- static void collectFiles(const std::string dirPath,
- std::vector<std::string>& dirs, unsigned int level,
- SymbolTable* symbols, std::vector<SymbolId>& files);
- static std::vector<SymbolId> collectFiles(std::string pathSpec,
- SymbolTable* symbols);
+ static std::vector<SymbolId> collectFiles(const std::string &pathSpec,
+ SymbolTable *const symbols);
static std::string getFileContent(const std::string name);
static std::string getPreferredPath(const std::string &path);
static std::string makeRelativePath(std::string path);


message:
Issue
On Windows, debug information is not included as part of the
library/executable itself. It is in a separate pdb file and
is required to be deployed to support debugging for projects
dependent on it.
Resolution
Deploy teh PDB files along side the libraries and binaries.

diff:
@@ -88,6 +88,9 @@ if(MSVC)
set(CMAKE_CXX_FLAGS_DEBUG
"${CMAKE_CXX_FLAGS_DEBUG} ${TCMALLOC_COMPILE_OPTIONS} /Zc:__cplusplus /W0 /bigobj ${MY_CXX_WARNING_FLAGS}"
)
+ set(CMAKE_CXX_FLAGS_RELWITHDEBINFO
+ "${CMAKE_CXX_FLAGS_RELEASE} ${TCMALLOC_COMPILE_OPTIONS} /Zc:__cplusplus /W0 /bigobj ${MY_CXX_WARNING_FLAGS}"
+ )
set(CMAKE_CXX_FLAGS_RELEASE
"${CMAKE_CXX_FLAGS_RELEASE} ${TCMALLOC_COMPILE_OPTIONS} /Zc:__cplusplus /W0 /bigobj ${MY_CXX_WARNING_FLAGS}"
)
@@ -300,17 +303,20 @@ set_target_properties(surelog PROPERTIES PUBLIC_HEADER "${SURELOG_PUBLIC_HEADERS
add_executable(surelog-bin ${PROJECT_SOURCE_DIR}/src/main.cpp)
set_target_properties(surelog-bin PROPERTIES OUTPUT_NAME surelog)
+
+add_executable(hellosureworld ${PROJECT_SOURCE_DIR}/src/hellosureworld.cpp)
+add_executable(hellouhdm ${PROJECT_SOURCE_DIR}/src/hellouhdm.cpp)
+
if(MSVC)
# We have two files named "surelog.lib" and both getting generated in the lib folder
# One is the surelog.lib generated by the surelog target and the other is the one generated
# becaues of /IMPLIB option when linking the executable. Unfortunately, there is no documented
# way to disable the latter in CMake. So, moving the library to the bin directory (right next to the exe)
set_target_properties(surelog-bin PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
+ set_target_properties(hellouhdm PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
+ set_target_properties(hellosureworld PROPERTIES ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/bin)
endif()
-add_executable(hellosureworld ${PROJECT_SOURCE_DIR}/src/hellosureworld.cpp)
-add_executable(hellouhdm ${PROJECT_SOURCE_DIR}/src/hellouhdm.cpp)
-
target_link_libraries(surelog-bin PUBLIC surelog)
target_link_libraries(surelog PUBLIC antlr4_static)
target_link_libraries(surelog PUBLIC flatbuffers)
@@ -518,3 +524,20 @@ install(
${PROJECT_SOURCE_DIR}/src/Expression/Expr.h
${PROJECT_SOURCE_DIR}/src/Expression/Value.h
DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/surelog/Expression)
+
+if (WIN32)
+ install(
+ FILES $<TARGET_PDB_FILE:surelog-bin>
+ ${Python3_RUNTIME_LIBRARY_DIRS}/python${Python3_VERSION_MAJOR}${Python3_VERSION_MINOR}$<$<CONFIG:Debug>:_d>.dll
+ DESTINATION ${CMAKE_INSTALL_BINDIR})
+ install(
+ FILES ${CMAKE_CURRENT_BINARY_DIR}/CMakeFiles/surelog.dir/surelog.pdb
+ ${FlatBuffers_BINARY_DIR}/CMakeFiles/flatbuffers.dir/flatbuffers.pdb
+ ${LIBANTLR4_BINARY_DIR}/runtime/CMakeFiles/antlr4_static.dir/antlr4_static.pdb
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/surelog)
+ install(
+ FILES ${UHDM_BINARY_DIR}/CMakeFiles/uhdm.dir/uhdm.pdb
+ ${UHDM_BINARY_DIR}/third_party/capnproto/c++/src/capnp/CMakeFiles/capnp.dir/capnp.pdb
+ ${UHDM_BINARY_DIR}/third_party/capnproto/c++/src/kj/CMakeFiles/kj.dir/kj.pdb
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/uhdm)
+endif()


message: A '*' shouldn't include a path separator

diff:
@@ -206,7 +206,7 @@ std::vector<SymbolId> FileUtils::collectFiles(const std::string& pathSpec,
R"([a-zA-Z0-9_\-.]+)" + escaped + R"([a-zA-Z0-9_\-.]+)" + escaped); // separator NOT allowed
regexp = StringUtils::replaceAll(regexp, ".", "\\."); // escape it
regexp = StringUtils::replaceAll(regexp, "?", R"([a-zA-Z0-9_\-\.])"); // at most one
- regexp = StringUtils::replaceAll(regexp, "*", ".*"); // free for all
+ regexp = StringUtils::replaceAll(regexp, "*", "[^" + escaped + "]*"); // free for all
const std::regex regex(regexp);
const fs::directory_options options =


message:
revert recent breaking changes to send
This will allow us to tag the next Kitura version without incrementing the major.
The breaking changes will be deferred and put back in for Kitura 3 some time in the future.

diff:
@@ -272,8 +272,10 @@ extension Router {
let status = self.httpStatusCode(from: err)
response.status(status)
} else {
+ let encoded = try JSONEncoder().encode(result)
response.status(.created)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error
@@ -325,9 +327,11 @@ extension Router {
next()
return
}
+ let encoded = try JSONEncoder().encode(result)
response.status(.created)
response.headers["Location"] = String(id.value)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error
@@ -374,8 +378,10 @@ extension Router {
let status = self.httpStatusCode(from: err)
response.status(status)
} else {
+ let encoded = try JSONEncoder().encode(result)
response.status(.OK)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error
@@ -422,8 +428,10 @@ extension Router {
let status = self.httpStatusCode(from: err)
response.status(status)
} else {
+ let encoded = try JSONEncoder().encode(result)
response.status(.OK)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error
@@ -452,8 +460,10 @@ extension Router {
let status = self.httpStatusCode(from: err)
response.status(status)
} else {
+ let encoded = try JSONEncoder().encode(result)
response.status(.OK)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error
@@ -476,8 +486,10 @@ extension Router {
let status = self.httpStatusCode(from: err)
response.status(status)
} else {
+ let encoded = try JSONEncoder().encode(result)
response.status(.OK)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error
@@ -500,8 +512,10 @@ extension Router {
let status = self.httpStatusCode(from: err)
response.status(status)
} else {
+ let encoded = try JSONEncoder().encode(result)
response.status(.OK)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error
@@ -536,8 +550,10 @@ extension Router {
let status = self.httpStatusCode(from: err)
response.status(status)
} else {
+ let encoded = try JSONEncoder().encode(result)
response.status(.OK)
- try response.send(result)
+ response.headers.setType("json")
+ response.send(data: encoded)
}
} catch {
// Http 500 error

diff:
@@ -265,17 +265,20 @@ public class RouterResponse {
///
/// - Parameter json: The array to send in JSON format.
/// - Returns: this RouterResponse.
- /// - Throws: if the the object could not be encoded using the JSONSerializationType
@discardableResult
- public func send(json: [Any]) throws -> RouterResponse {
+ public func send(json: [Any]) -> RouterResponse {
guard !state.invokedEnd else {
Log.warning("RouterResponse send(json:) invoked after end() for \(self.request.urlURL)")
return self
}
+ do {
let jsonData = try JSONSerializationType.data(withJSONObject: json, options:.prettyPrinted)
headers.setType("json")
send(data: jsonData)
+ } catch {
+ Log.warning("Failed to convert JSON for sending: \(error.localizedDescription)")
+ }
return self
}
@@ -284,17 +287,20 @@ public class RouterResponse {
///
/// - Parameter json: The Dictionary to send in JSON format as a hash.
/// - Returns: this RouterResponse.
- /// - Throws: if the the object could not be encoded using the JSONSerializationType
@discardableResult
- public func send(json: [String: Any]) throws -> RouterResponse {
+ public func send(json: [String: Any]) -> RouterResponse {
guard !state.invokedEnd else {
Log.warning("RouterResponse send(json:) invoked after end() for \(self.request.urlURL)")
return self
}
+ do {
let jsonData = try JSONSerializationType.data(withJSONObject: json, options:.prettyPrinted)
headers.setType("json")
send(data: jsonData)
+ } catch {
+ Log.warning("Failed to convert JSON for sending: \(error.localizedDescription)")
+ }
return self
}
@@ -441,16 +447,18 @@ extension RouterResponse {
///
/// - Parameter obj: the Codable object to send.
/// - Returns: this RouterResponse.
- /// - Throws: if the the object could not be encoded using the JSONEncoder
@discardableResult
- public func send<T : Encodable>(_ obj: T) throws -> RouterResponse {
+ public func send<T : Encodable>(_ obj: T) -> RouterResponse {
guard !state.invokedEnd else {
Log.warning("RouterResponse send(_ obj:) invoked after end() for \(self.request.urlURL)")
return self
}
-
+ do {
headers.setType("json")
send(data: try encoder.encode(obj))
+ } catch {
+ Log.warning("Failed to encode Codable object for sending: \(error.localizedDescription)")
+ }
return self
}
@@ -459,10 +467,9 @@ extension RouterResponse {
///
/// - Parameter json: the Encodable object to send.
/// - Returns: this RouterResponse.
- /// - Throws: if the the object could not be encoded using the JSONEncoder
@discardableResult
- public func send<T : Encodable>(json: T) throws -> RouterResponse {
- return try send(json)
+ public func send<T : Encodable>(json: T) -> RouterResponse {
+ return send(json)
}
/// Send JSON with JSONP callback.

diff:
@@ -1449,9 +1449,9 @@ class TestResponse: KituraTest {
try response.send(status: HTTPStatusCode.forbidden).send(data: "<!DOCTYPE html>forbidden\n\n".data(using: .utf8)!).end()
try response.send(status: HTTPStatusCode.OK).end()
response.send("string")
- try response.send(json: json)
- try response.send(json: ["some": "json"])
- try response.send(json: ["some", 10, "json"])
+ response.send(json: json)
+ response.send(json: ["some": "json"])
+ response.send(json: ["some", 10, "json"])
try response.send(jsonp: json, callbackParameter: "cb").end()
let data = try TestResponse.encoder.encode(json)

diff:
@@ -157,7 +157,7 @@ class TestSubrouter: KituraTest {
}
let handler: RouterHandler = { (req: RouterRequest, res: RouterResponse, next: () -> Void) throws in
- try res.send(json: req.parameters)
+ res.send(json: req.parameters)
}
let router = Router()
@@ -168,7 +168,7 @@ class TestSubrouter: KituraTest {
subsubRouter1.all("/subsub2/passthrough", handler: handler)
router.route("/root2/:root2", mergeParameters: true).all { req, res, _ in
- try res.send(json: req.parameters)
+ res.send(json: req.parameters)
}
performServerTest(router, asyncTasks: { expectation in

repo: kitura/kitura

original_message:
revert recent breaking changes to send (#1213)

This will allow us to tag the next Kitura version without incrementing the major.
The breaking changes will be deferred and put back in for Kitura 3 some time in the future.


diff:
@@ -420,12 +420,27 @@ public struct CodableHelpers {
* - Returns: True if the content type of the request is application/json, false otherwise
*/
public static func isContentTypeJSON(_ request: RouterRequest) -> Bool {
+ // FIXME: This should be a simple lookup of content type cached on the RouterRequest
guard let contentType = request.headers["Content-Type"] else {
return false
}
return contentType.hasPrefix("application/json")
}
+ /**
+ * Check if the given request has content type x-www-form-urlencoded
+ *
+ * - Parameter request: The RouterRequest to check
+ * - Returns: True if the content type of the request is application/x-www-form-urlencoded, false otherwise
+ */
+ public static func isContentTypeURLEncoded(_ request: RouterRequest) -> Bool {
+ // FIXME: This should be a simple lookup of content type cached on the RouterRequest
+ guard let contentType = request.headers["Content-Type"] else {
+ return false
+ }
+ return contentType.hasPrefix("application/x-www-form-urlencoded")
+ }
+
/**
* Get the HTTPStatusCode corresponding to the provided RequestError
*
@@ -616,7 +631,7 @@ public struct CodableHelpers {
* - Returns: An instance of `InputType` representing the decoded body data.
*/
public static func readCodableOrSetResponseStatus<InputType: Codable>(_ inputCodableType: InputType.Type, from request: RouterRequest, response: RouterResponse) -> InputType? {
- guard CodableHelpers.isContentTypeJSON(request) else {
+ guard CodableHelpers.isContentTypeJSON(request) || CodableHelpers.isContentTypeURLEncoded(request) else {
response.status(.unsupportedMediaType)
return nil
}
@@ -633,6 +648,7 @@ public struct CodableHelpers {
return nil
}
}
+
/**
* Read an id from the request URL, setting an error status on the given response in the case of failure.
*

diff:
@@ -19,6 +19,7 @@ import Socket
import LoggerAPI
import Foundation
+import KituraContracts
// MARK: RouterRequest
@@ -172,14 +173,23 @@ public class RouterRequest {
return try serverRequest.read(into: &data)
}
- /// Read the body of the request as a Codable object.
- ///
- /// - Parameter type: Codable object to which the body of the request will be converted.
+ /// Read the body of the request as a Codable object. It can decode JSON or URLEncoded forms.
+ /// It chooses the decoder by looking up the Content-Type header.
+ /// If there is no header it defaults to JSONDecoder.
+ /// - Parameter as: Codable object to which the body of the request will be converted.
/// - Throws: Socket.Error if an error occurred while reading from a socket.
/// - Throws: `DecodingError.dataCorrupted` if values requested from the payload are corrupted, or if the given data is not valid JSON.
/// - Throws: An error if any value throws an error during decoding.
/// - Returns: The instantiated Codable object
public func read<T: Decodable>(as type: T.Type) throws -> T {
+ // FIXME: RouterRequest should cache the content type, so that this is just a lookup
+ if CodableHelpers.isContentTypeURLEncoded(self) {
+ let body = try self.readString()
+ guard let urlKeyValuePairs = body?.urlDecodedFieldValuePairs else {
+ throw Error.failedToParseRequestBody(body: body ?? "Failed to read body as String")
+ }
+ return try QueryDecoder(dictionary: urlKeyValuePairs).decode(type)
+ }
var data = Data()
_ = try serverRequest.read(into: &data)
return try JSONDecoder().decode(type, from: data)

diff:
@@ -18,6 +18,7 @@ import XCTest
import Kitura
@testable import KituraNet
+@testable import KituraContracts
import Foundation
import Dispatch
@@ -42,6 +43,7 @@ import Dispatch
protocol RequestTestBuilder {
func request(_ method: String, path: String) -> AssertionTestBuilder
func request<T: Encodable>(_ method: String, path: String, data: T) -> AssertionTestBuilder
+ func request(_ method: String, path: String, urlEncodedString: String) -> AssertionTestBuilder
func run()
}
@@ -84,6 +86,16 @@ class ServerTestBuilder: RequestTestBuilder, AssertionTestBuilder {
})
}
}
+
+ init(_ test: KituraTest, _ method: String, _ path: String, _ urlEncodedString: String) {
+ self.test = test
+ self.invoker = { callback in
+ test.performRequest(method, path: path, callback: callback, requestModifier: { request in
+ request.headers["Content-Type"] = "application/x-www-form-urlencoded"
+ request.write(from: urlEncodedString)
+ })
+ }
+ }
}
let test: KituraTest
let router: ServerDelegate
@@ -111,6 +123,11 @@ class ServerTestBuilder: RequestTestBuilder, AssertionTestBuilder {
return self
}
+ public func request(_ method: String, path: String, urlEncodedString: String) -> AssertionTestBuilder {
+ requests.append(Request(test, method, path, urlEncodedString))
+ return self
+ }
+
public func has(callback: @escaping (ClientResponse) -> Void) -> Self {
currentRequest?.assertions.append(callback)
return self

diff:
@@ -19,7 +19,6 @@ import Foundation
import KituraContracts
@testable import Kitura
-@testable import KituraNet
class TestCodableRouter: KituraTest {
static var allTests: [(String, (TestCodableRouter) -> () throws -> Void)] {
@@ -146,6 +145,10 @@ class TestCodableRouter: KituraTest {
print("POST on /bodyerror/users for user \(user)")
respondWith(nil, RequestError(.conflict, body: Conflict(on: "id")))
}
+ router.post("/urlencoded") { (user: User, respondWith: (User?, RequestError?) -> Void) in
+ print("POST on /urlencoded for user \(user)")
+ respondWith(user, nil)
+ }
let user = User(id: 4, name: "David")
buildServerTest(router, timeout: 30)
@@ -163,6 +166,26 @@ class TestCodableRouter: KituraTest {
.hasContentType(withPrefix: "application/json")
.hasData(Conflict(on: "id"))
+ .request("post", path: "/urlencoded", urlEncodedString: "id=4&name=David")
+ .hasStatus(.created)
+ .hasContentType(withPrefix: "application/json")
+ .hasData(user)
+
+ .request("post", path: "/urlencoded", urlEncodedString: "id=4&name=David&extra=yes")
+ .hasStatus(.created)
+ .hasContentType(withPrefix: "application/json")
+ .hasData(user)
+
+ .request("post", path: "/urlencoded", urlEncodedString: "encoding=valid&failed=match")
+ .hasStatus(.unprocessableEntity)
+ .hasNoData()
+
+ .request("post", path: "/urlencoded", urlEncodedString: "invalidEncoding")
+ .hasStatus(.unprocessableEntity)
+ .hasNoData()
+
+
+
.run()
}

original_message: feat: add route handling for urlencoded forms (#1228)


message: feat: Add codable identifier arrays

diff:
@@ -85,6 +85,25 @@ extension Router {
getSafely(route, handler: handler)
}
+ /**
+ Setup a IdentifierCodableArrayClosure on the provided route which will be invoked when a request comes to the server.
+ ### Usage Example: ###
+ ````
+ //User is a struct object that conforms to Codable
+ router.get("/users") { (respondWith: ([(Int, User)]?, RequestError?) -> Void) in
+
+ ...
+
+ respondWith([(Int, User)], nil)
+ }
+ ````
+ - Parameter route: A String specifying the pattern that needs to be matched, in order for the handler to be invoked.
+ - Parameter handler: A IdentifierCodableArrayClosure that gets invoked when a request comes to the server.
+ */
+ public func get<Id: Identifier, O: Codable>(_ route: String, handler: @escaping IdentifierCodableArrayClosure<Id, O>) {
+ getSafely(route, handler: handler)
+ }
+
/**
Setup a (QueryParams, CodableArrayResultClosure) -> Void on the provided route which will be invoked when a request comes to the server.
@@ -322,6 +341,14 @@ extension Router {
}
}
+ // Get array of (Id, Codable) tuples
+ fileprivate func getSafely<Id: Identifier, O: Codable>(_ route: String, handler: @escaping IdentifierCodableArrayClosure<Id, O>) {
+ get(route) { request, response, next in
+ Log.verbose("Received GET (plural with identifier) type-safe request")
+ handler(CodableHelpers.constructTupleArrayOutResultHandler(response: response, completion: next))
+ }
+ }
+
// Get w/Query Parameters
fileprivate func getSafely<Q: QueryParams, O: Codable>(_ route: String, handler: @escaping (Q, @escaping CodableArrayResultClosure<O>) -> Void) {
get(route) { request, response, next in
@@ -464,7 +491,7 @@ public struct CodableHelpers {
*
* - Parameter response: The `RouterResponse` to which the codable response error and
* status code will be written
- * - Parameter completion: The completion to be called after the when the returned
+ * - Parameter completion: The completion to be called after the returned
* closure completes execution.
* - Returns: The closure to pass to the codable route handler. The closure takes one argument
* `(RequestError?)`.
@@ -474,7 +501,7 @@ public struct CodableHelpers {
* If successful, the HTTP status code will be set to `HTTPStatusCode.noContent` and no
* body will be sent.
*
- * If failed, the HTTP status code used for the response wll be set to either the
+ * If failed, the HTTP status code used for the response will be set to either the
* `httpCode` of the `RequestError`, if that is a valid HTTP status code, or
* `HTTPStatusCode.unknown` otherwise. If the `RequestError` has a codable `body` then
* it will be encoded and sent as the body of the response.
@@ -510,18 +537,18 @@ public struct CodableHelpers {
* - Parameter successStatus: The `HTTPStatusCode` to use for a successful response (see below)
* - Parameter response: The `RouterResponse` to which the codable response body (or codable
* error) and status code will be written
- * - Parameter completion: The completion to be called after the when the returned
+ * - Parameter completion: The completion to be called after the returned
* closure completes execution.
* - Returns: The closure to pass to the codable route handler. The closure takes two arguments
* `(OutputType?, RequestError?)`.
* If the second (error) argument is `nil` then the first (body) argument should be non-`nil`
* and the response will be considered successful. If the second (error) argument is non-`nil`
- * then the first argument is ignore and the response is considered failed.
+ * then the first argument is ignored and the response is considered failed.
*
- * If successful, the HTTP status code will be set to `successStatus` and the first argument
- * will be encoded and sent as the body of the response.
+ * If successful, the HTTP status code will be set to `successStatus` and the `CodableResultClosure` output
+ * will be JSON encoded and sent as the body of the response.
*
- * If failed, the HTTP status code used for the response wll be set to either the
+ * If failed, the HTTP status code used for the response will be set to either the
* `httpCode` of the `RequestError`, if that is a valid HTTP status code, or
* `HTTPStatusCode.unknown` otherwise. If the `RequestError` has a codable `body` then
* it will be encoded and sent as the body of the response.
@@ -554,6 +581,62 @@ public struct CodableHelpers {
}
}
+ /**
+ * Create a closure that can be called by a codable route handler that
+ * provides an array of tuples of (Identifier, Codable) and an optional `RequestError`
+ *
+ * - Note: This function is intended for use by the codable router or extensions
+ * thereof. It will create a closure that can be passed to the registered
+ * route handler.
+ *
+ * - Parameter successStatus: The `HTTPStatusCode` to use for a successful response (see below)
+ * - Parameter response: The `RouterResponse` to which the codable response body (or codable
+ * error) and status code will be written
+ * - Parameter completion: The completion to be called after the returned
+ * closure completes execution.
+ * - Returns: The closure to pass to the codable route handler. The closure takes two arguments
+ * `([(Id, OutputType)]?, RequestError?)`.
+ * If the second (error) argument is `nil` then the first argument (body) should be non-`nil`
+ * and the response will be considered successful. If the second (error) argument is non-`nil`
+ * then the first argument is ignored and the response is considered failed.
+ *
+ * If successful, the HTTP status code will be set to `successStatus` and the `IdentifierCodableArrayResultClosure` output
+ * will be JSON encoded as an array of dictionaries, which is then sent as the body of the response.
+ *
+ * If failed, the HTTP status code used for the response will be set to either the
+ * `httpCode` of the `RequestError`, if that is a valid HTTP status code, or
+ * `HTTPStatusCode.unknown` otherwise. If the `RequestError` has a codable `body` then
+ * it will be encoded and sent as the body of the response.
+ */
+ public static func constructTupleArrayOutResultHandler<Id: Identifier, OutputType: Codable>(successStatus: HTTPStatusCode = .OK, response: RouterResponse, completion: @escaping () -> Void) -> IdentifierCodableArrayResultClosure<Id, OutputType> {
+ return { codableOutput, error in
+ if let error = error {
+ response.status(httpStatusCode(from: error))
+ do {
+ if let bodyData = try error.encodeBody(.json) {
+ response.headers.setType("json")
+ response.send(data: bodyData)
+ }
+ } catch {
+ Log.error("Could not encode error: \(error)")
+ response.status(.internalServerError)
+ }
+ } else {
+ do {
+ let entries = codableOutput?.map({ [$0.value: $1] })
+ let encoded = try JSONEncoder().encode(entries)
+ response.headers.setType("json")
+ response.send(data: encoded)
+ response.status(successStatus)
+ } catch {
+ Log.error("Could not encode result: \(error)")
+ response.status(.internalServerError)
+ }
+ }
+ completion()
+ }
+ }
+
/**
* Create a closure that can be called by a codable route handler that
* provides an optional `Identifier` id, optional `Codable` body and an optional `RequestError`
@@ -565,7 +648,7 @@ public struct CodableHelpers {
* - Parameter successStatus: The `HTTPStatusCode` to use for a successful response (see below)
* - Parameter response: The `RouterResponse` to which the id, codable response body (or codable
* error) and status code will be written
- * - Parameter completion: The completion to be called after the when the returned
+ * - Parameter completion: The completion to be called after the returned
* closure completes execution.
* - Returns: The closure to pass to the codable route handler. The closure takes three arguments
* `(IdType?, OutputType?, RequestError?)`.
@@ -574,11 +657,11 @@ public struct CodableHelpers {
* (error) argument is non-`nil` then the first and second arguments are ignored and the
* response is considered failed.
*
- * If successful, the HTTP status code will be set to `successStatus`, the first argument
- * will be encoded and sent as the body of the response, and the `Location` header of the
+ * If successful, the HTTP status code will be set to `successStatus`, the `IdentifierCodableResultClosure` output
+ * will be JSON encoded and sent as the body of the response, and the `Location` header of the
* response will be set to the id (by converting it to a `String` using its `value` property).
*
- * If failed, the HTTP status code used for the response wll be set to either the
+ * If failed, the HTTP status code used for the response will be set to either the
* `httpCode` of the `RequestError`, if that is a valid HTTP status code, or
* `HTTPStatusCode.unknown` otherwise. If the `RequestError` has a codable `body` then
* it will be encoded and sent as the body of the response.

diff:
@@ -57,6 +57,7 @@ protocol AssertionTestBuilder: RequestTestBuilder {
func hasData(_ expected: String) -> Self
func hasData<T: Decodable & Equatable>(_ expected: [T]) -> Self
func hasData<T: Decodable & Equatable>(_ expected: T) -> Self
+ func hasData<T: Decodable & Equatable>(_ expected: [[String: T]]) -> Self
}
// A builder object for constructing tests made up of one or more
@@ -238,6 +239,23 @@ class ServerTestBuilder: RequestTestBuilder, AssertionTestBuilder {
}
}
+ func hasData<T: Decodable & Equatable>(_ expected: [[String : T]]) -> Self {
+ return has { response in
+ guard let (_, data) = self.readDataOrFail(from: response) else { return }
+ do {
+ let actual = try JSONDecoder().decode([[String : T]].self, from: data)
+ for (index, tuple) in actual.enumerated() {
+ let tupleKey = Array(tuple.keys)[0]
+ let expectedKey = Array(expected[index].keys)[0]
+ XCTAssertEqual(tupleKey, expectedKey, "Response data does not match expected key:\nexpected: \(tupleKey)\nactual: \(expectedKey)")
+ XCTAssertEqual(tuple[tupleKey], expected[index][expectedKey], "Response data does not match expected value:\nexpected: \(String(describing: tuple[tupleKey]))\nactual: \(String(describing: expected[index][expectedKey]))")
+ }
+ } catch {
+ XCTFail("Failed to decode response data into type \(T.self): \(error)")
+ }
+ }
+ }
+
public func run() {
// Construct a list of async tasks that will perform each
// request in turn and test their associated assertions

diff:
@@ -28,6 +28,7 @@ class TestCodableRouter: KituraTest {
("testBasicGetSingleton", testBasicGetSingleton),
("testBasicGetArray", testBasicGetArray),
("testBasicGetSingle", testBasicGetSingle),
+ ("testBasicGetIdentifiersArray", testBasicGetIdentifiersArray),
("testBasicDelete", testBasicDelete),
("testBasicDeleteSingle", testBasicDeleteSingle),
("testBasicPut", testBasicPut),
@@ -287,6 +288,48 @@ class TestCodableRouter: KituraTest {
.run()
}
+ func testBasicGetIdentifiersArray() {
+ var intTuple = [(Int, User)]()
+ self.userStore.forEach { intTuple.append(($0.0, $0.1)) }
+ let expectedIntData: [[String: User]] = intTuple.map({ [$0.value: $1] })
+
+ var stringTuple = [(String, User)]()
+ self.userStore.forEach { stringTuple.append((String($0.0), $0.1)) }
+ let expectedStringData: [[String: User]] = stringTuple.map({ [$0.value: $1] })
+
+ router.get("/int/users") { (respondWith: ([(Int, User)]?, RequestError?) -> Void) in
+ print("GET on /int/users")
+ respondWith(intTuple, nil)
+ }
+
+ router.get("/string/users") { (respondWith: ([(String, User)]?, RequestError?) -> Void) in
+ print("GET on /string/users")
+ respondWith(stringTuple, nil)
+ }
+
+ router.get("/error/users") { (respondWith: ([(String, User)]?, RequestError?) -> Void) in
+ print("GET on /error/users")
+ respondWith(nil, .serviceUnavailable)
+ }
+
+ buildServerTest(router, timeout: 30)
+ .request("get", path: "/int/users")
+ .hasStatus(.OK)
+ .hasContentType(withPrefix: "application/json")
+ .hasData(expectedIntData)
+
+ .request("get", path: "/string/users")
+ .hasStatus(.OK)
+ .hasContentType(withPrefix: "application/json")
+ .hasData(expectedStringData)
+
+ .request("get", path: "/error/users")
+ .hasStatus(.serviceUnavailable)
+ .hasNoData()
+
+ .run()
+ }
+
func testBasicGetSingle() {
router.get("/users") { (id: Int, respondWith: (User?, RequestError?) -> Void) in
print("GET on /users/\(id)")

original_message: feat: Add codable identifier arrays (#1223)


message: made staticFileServer class open

diff:
@@ -19,7 +19,7 @@ import Foundation
// MARK: StaticFileServer
/// A router middleware that serves static files from a given path.
-public class StaticFileServer: RouterMiddleware {
+open class StaticFileServer: RouterMiddleware {
/// Cache configuration options for StaticFileServer.
public struct CacheOptions {
@@ -108,7 +108,7 @@ public class StaticFileServer: RouterMiddleware {
/// HTTP request.
/// - Parameter next: The closure called to invoke the next handler or middleware
/// associated with the request.
- public func handle(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
+ open func handle(request: RouterRequest, response: RouterResponse, next: @escaping () -> Void) {
defer {
next()
}

original_message: made staticFileServer class open (#1245)


message: Add asRaw to ParsedBody

diff:
@@ -69,6 +69,18 @@ public indirect enum ParsedBody {
}
}
+ /// Extract a "raw" body from the `ParsedBody` enum
+ ///
+ /// - Returns: The "raw" body as a Data, or nil if the body wasn't in raw format.
+ public var asRaw: Data? {
+ switch self {
+ case .raw(let body):
+ return body
+ default:
+ return nil
+ }
+ }
+
/// Extract a "text" body from the `ParsedBody` enum
///
/// - Returns: The "text" body as a String, or nil if the body wasn't in text format.

diff:
@@ -1221,7 +1221,7 @@ class TestResponse: KituraTest {
} catch {
XCTFail("caught error: \(error)")
}
- } else if case let .raw(data) = requestBody {
+ } else if let data = requestBody.asRaw {
XCTAssertNotNil(data)
let length = "2048"
_ = response.send("length: \(length)")

original_message: Add asRaw to ParsedBody (#1258)


message: docs: Update BodyParser documentation

diff:
@@ -21,12 +21,39 @@ import Foundation
// MARK: BodyParser
-/// The `BodyParser` parses the body of the request prior to sending it to the handler. It reads the Content-Type of the message header and populates the `RouterRequest` body field with a `ParsedBody` enumeration (e.g. json, raw, text, urlEncoded).
-/// In order for the BodyParser to be used it must first be registered with any routes that are interested in the ParsedBody payload.
+/// The `BodyParser` parses the body of the request prior to sending it to the handler. It reads the Content-Type of the message header and populates the `RouterRequest` body field with a corresponding `ParsedBody` enumeration.
+///
+/// In order for the BodyParser to be used it must first be registered with any routes that are interested in the `ParsedBody` payload.
+///
+/// ### ParsedBody enumeration: ###
+///
+/// The mappings from the incoming Content-Type to an internal representation of the body are as follows:
+///
+/// ```swift
+/// .json([String: Any]) // "application/json"
+/// .text(String) // "text/*"
+/// .urlEncoded([String:String]) // "application/x-www-form-urlencoded"
+/// .multipart([Part]) // "multipart/form-data"
+/// .raw(Data) // Any other Content-Type
+/// ```
+///
+/// Each case has a corresponding convenience property, e.g. `asURLEncoded: [String:String]`, for accessing the associated data.
+///
+/// __Note__: If you have not declared a Content-Type header, `ParsedBody` will be `nil`.
+///
/// ### Usage Example: ###
-/// In this example, all routes to the BodyParser middleware are registered to the `BodyParser` middleware.
+///
+/// In this example, all routes to the BodyParser middleware are registered to the `BodyParser` middleware. A request with "application/json", ContentType header is received. It is then parsed as JSON and the value for "name" is returned in the response.
/// ```swift
-/// router.all("/*", middleware: BodyParser())
+/// router.all("/name", middleware: BodyParser())
+/// router.post("/name") { request, response, next in
+/// guard let jsonBody = request.parsedBody?.asJSON else {
+/// next()
+/// return
+/// }
+/// let name = jsonBody["name"] as? String ?? ""
+/// try response.send("Hello \(name)").end()
+/// }
/// ```
/// __Note__: When using Codable Routing in Kitura 2.x the BodyParser should not be registered to any codable routes (doing so will log the following error "No data in request. Codable routes do not allow the use of a BodyParser." and the route handler will not be executed).
public class BodyParser: RouterMiddleware {

original_message: docs: Update BodyParser documentation (#1260)


message: fix: remove trailing Whitespace

diff:
@@ -111,7 +111,6 @@ class TestTypeSafeMiddleware: KituraTest {
respondWith(user, nil)
}
-
buildServerTest(router, timeout: 30)
.request("get", path: "/userMiddleware", headers: ["TestHeader": "Hello"])
.hasStatus(.OK)
@@ -165,7 +164,6 @@ class TestTypeSafeMiddleware: KituraTest {
respondWith(userArray, nil)
}
-
buildServerTest(router, timeout: 30)
.request("get", path: "/userMiddleware", headers: ["TestHeader": "Hello"])
.hasStatus(.OK)
@@ -214,8 +212,7 @@ class TestTypeSafeMiddleware: KituraTest {
func testSingleMiddlewareGetIdentifier() {
// Expected user: User(id: 1, name: "Andy")
guard let user = userStore[1] else {
- print("no value found for userStore[1]")
- XCTFail()
+ XCTFail("no value found for userStore[1]")
return
}
@@ -225,7 +222,6 @@ class TestTypeSafeMiddleware: KituraTest {
respondWith(user, nil)
}
-
buildServerTest(router, timeout: 30)
.request("get", path: "/userMiddleware/1", headers: ["TestHeader": "Hello"])
.hasStatus(.OK)
@@ -242,13 +238,12 @@ class TestTypeSafeMiddleware: KituraTest {
func testMultipleMiddlewareGetIdentifier() {
// Expected user: User(id: 1, name: "Andy")
guard let user = userStore[1] else {
- print("no value found for userStore[1]")
- XCTFail()
+ XCTFail("no value found for userStore[1]")
return
}
router.get("/userMultiMiddleware") { (middleware: UserMiddleware, middleware2: UserMiddleware2, middleware3: UserMiddleware3, id: Int, respondWith: (User?, RequestError?) -> Void) in
- print("GET with identifier on /userMultiMiddleware - received headers \(middleware.header), \(middleware2.header), \(middleware3.header)")
+ print("GET with identifier: \(id) on /userMultiMiddleware - received headers \(middleware.header), \(middleware2.header), \(middleware3.header)")
respondWith(user, nil)
}
@@ -289,7 +284,6 @@ class TestTypeSafeMiddleware: KituraTest {
respondWith(intTuple, nil)
}
-
buildServerTest(router, timeout: 30)
.request("get", path: "/userMiddleware", headers: ["TestHeader": "Hello"])
.hasStatus(.OK)
@@ -340,12 +334,10 @@ class TestTypeSafeMiddleware: KituraTest {
.run()
}
-
func testSingleMiddlewareGetSingletonParameters() {
// Expected user: User(id: 1, name: "Andy")
guard let user = userStore[1] else {
- print("no value found for userStore[1]")
- XCTFail()
+ XCTFail("no value found for userStore[1]")
return
}
@@ -355,7 +347,6 @@ class TestTypeSafeMiddleware: KituraTest {
respondWith(user, nil)
}
-
buildServerTest(router, timeout: 30)
.request("get", path: "/userMiddleware?id=1", headers: ["TestHeader": "Hello"])
.hasStatus(.OK)
@@ -372,13 +363,12 @@ class TestTypeSafeMiddleware: KituraTest {
func testMultipleMiddlewareGetSingletonParameters() {
// Expected user: User(id: 1, name: "Andy")
guard let user = userStore[1] else {
- print("no value found for userStore[1]")
- XCTFail()
+ XCTFail("no value found for userStore[1]")
return
}
router.get("/userMultiMiddleware") { (middleware: UserMiddleware, middleware2: UserMiddleware2, middleware3: UserMiddleware3, query: MyQuery, respondWith: (User?, RequestError?) -> Void) in
- print("GET single with parameters on /userMultiMiddleware - received headers \(middleware.header), \(middleware2.header), \(middleware3.header)")
+ print("GET single with parameters: \(query) on /userMultiMiddleware - received headers \(middleware.header), \(middleware2.header), \(middleware3.header)")
respondWith(user, nil)
}
@@ -406,7 +396,6 @@ class TestTypeSafeMiddleware: KituraTest {
.run()
}
-
func testSingleMiddlewareGetArrayParameters() {
let userArray = [User(id: 1, name: "Andy"), User(id: 2, name: "Dave"), User(id: 3, name: "Ian")]
let expectedArray = [User(id: 1, name: "Andy"), User(id: 2, name: "Dave")]
@@ -417,7 +406,6 @@ class TestTypeSafeMiddleware: KituraTest {
respondWith(matchedUsers, nil)
}
-
buildServerTest(router, timeout: 30)
.request("get", path: "/userMiddleware?id=2", headers: ["TestHeader": "Hello"])
.hasStatus(.OK)
@@ -473,7 +461,6 @@ class TestTypeSafeMiddleware: KituraTest {
respondWith(nil)
}
-
buildServerTest(router, timeout: 30)
.request("delete", path: "/userMiddleware", headers: ["TestHeader": "Hello"])
.hasStatus(.noContent)
@@ -523,14 +510,13 @@ class TestTypeSafeMiddleware: KituraTest {
router.delete("/userMiddleware") { (middleware: UserMiddleware, id: Int, respondWith: (RequestError?) -> Void) in
print("DELETE on /userMiddleware - received header \(middleware.header)")
- guard let _ = self.userStore.removeValue(forKey: id) else {
+ guard self.userStore.removeValue(forKey: id) != nil else {
respondWith(.notFound)
return
}
respondWith(nil)
}
-
buildServerTest(router, timeout: 30)
.request("delete", path: "/userMiddleware/1", headers: ["TestHeader": "Hello"])
.hasStatus(.noContent)
@@ -548,7 +534,7 @@ class TestTypeSafeMiddleware: KituraTest {
router.delete("/userMultiMiddleware") { (middleware: UserMiddleware, middleware2: UserMiddleware2, middleware3: UserMiddleware3, id: Int, respondWith: (RequestError?) -> Void) in
print("DELETE on /userMultiMiddleware - received headers \(middleware.header), \(middleware2.header), \(middleware3.header)")
- guard let _ = self.userStore.removeValue(forKey: id) else {
+ guard self.userStore.removeValue(forKey: id) != nil else {
respondWith(.notFound)
return
}
@@ -566,7 +552,6 @@ class TestTypeSafeMiddleware: KituraTest {
.hasNoData()
.has { _ in XCTAssertNil(self.userStore[1]) }
-
// Test that Middleware2 fails when its header is missing, by examining the status (.notAcceptable)
.request("delete", path: "/userMultiMiddleware/1", headers: missing2ndHeader)
.hasStatus(.notAcceptable)
@@ -584,14 +569,13 @@ class TestTypeSafeMiddleware: KituraTest {
router.delete("/userMiddleware") { (middleware: UserMiddleware, query: MyQuery, respondWith: (RequestError?) -> Void) in
print("DELETE on /userMiddleware - received header \(middleware.header)")
- guard let _ = self.userStore.removeValue(forKey: query.id) else {
+ guard self.userStore.removeValue(forKey: query.id) != nil else {
respondWith(.notFound)
return
}
respondWith(nil)
}
-
buildServerTest(router, timeout: 30)
.request("delete", path: "/userMiddleware?id=1", headers: ["TestHeader": "Hello"])
.hasStatus(.noContent)
@@ -609,7 +593,7 @@ class TestTypeSafeMiddleware: KituraTest {
router.delete("/userMultiMiddleware") { (middleware: UserMiddleware, middleware2: UserMiddleware2, middleware3: UserMiddleware3, query: MyQuery, respondWith: (RequestError?) -> Void) in
print("DELETE on /userMultiMiddleware - received headers \(middleware.header), \(middleware2.header), \(middleware3.header)")
- guard let _ = self.userStore.removeValue(forKey: query.id) else {
+ guard self.userStore.removeValue(forKey: query.id) != nil else {
respondWith(.notFound)
return
}
@@ -627,7 +611,6 @@ class TestTypeSafeMiddleware: KituraTest {
.hasNoData()
.has { _ in XCTAssertNil(self.userStore[1]) }
-
// Test that Middleware2 fails when its header is missing, by examining the status (.notAcceptable)
.request("delete", path: "/userMultiMiddleware?id=1", headers: missing2ndHeader)
.hasStatus(.notAcceptable)
@@ -698,8 +681,7 @@ class TestTypeSafeMiddleware: KituraTest {
func testSingleMiddlewarePostIdentifier() {
// Expected user: User(id: 1, name: "Andy")
guard let user = userStore[1] else {
- print("no value found for userStore[1]")
- XCTFail()
+ XCTFail("no value found for userStore[1]")
return
}
@@ -726,8 +708,7 @@ class TestTypeSafeMiddleware: KituraTest {
func testMultipleMiddlewarePostIdentifier() {
// Expected user: User(id: 1, name: "Andy")
guard let user = userStore[1] else {
- print("no value found for userStore[1]")
- XCTFail()
+ XCTFail("no value found for userStore[1]")
return
}

original_message: fix: remove trailing Whitespace (#1283)


message: fix: added escaping to result closure

diff:
@@ -51,7 +51,7 @@ extension Router {
*/
public func get<T: TypeSafeMiddleware, O: Codable>(
_ route: String,
- handler: @escaping (T, CodableResultClosure<O>) -> Void
+ handler: @escaping (T, @escaping CodableResultClosure<O>) -> Void
) {
registerGetRoute(route: route, id: false, outputtype: O.self)
get(route) { request, response, next in
@@ -86,7 +86,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, @escaping CodableResultClosure<O>) -> Void
) {
registerGetRoute(route: route, id: false, outputtype: O.self)
get(route) { request, response, next in
@@ -121,7 +121,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, T3, @escaping CodableResultClosure<O>) -> Void
) {
registerGetRoute(route: route, id: false, outputtype: O.self)
get(route) { request, response, next in
@@ -161,7 +161,7 @@ extension Router {
*/
public func get<T: TypeSafeMiddleware, O: Codable>(
_ route: String,
- handler: @escaping (T, CodableArrayResultClosure<O>) -> Void
+ handler: @escaping (T, @escaping CodableArrayResultClosure<O>) -> Void
) {
registerGetRoute(route: route, id: false, outputtype: O.self)
get(route) { request, response, next in
@@ -196,7 +196,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, CodableArrayResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, @escaping CodableArrayResultClosure<O>) -> Void
) {
registerGetRoute(route: route, id: false, outputtype: O.self)
get(route) { request, response, next in
@@ -231,7 +231,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, CodableArrayResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, T3, @escaping CodableArrayResultClosure<O>) -> Void
) {
registerGetRoute(route: route, id: false, outputtype: O.self)
get(route) { request, response, next in
@@ -271,7 +271,7 @@ extension Router {
*/
public func get<T: TypeSafeMiddleware, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T, Id, CodableResultClosure<O>) -> Void
+ handler: @escaping (T, Id, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -313,7 +313,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, Id, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, Id, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -355,7 +355,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, Id, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, T3, Id, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -402,7 +402,7 @@ extension Router {
*/
public func get<T: TypeSafeMiddleware, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T, IdentifierCodableArrayResultClosure<Id, O>) -> Void
+ handler: @escaping (T, @escaping IdentifierCodableArrayResultClosure<Id, O>) -> Void
) {
registerGetRoute(route: route, id: true, outputtype: O.self)
get(route) { request, response, next in
@@ -437,7 +437,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, IdentifierCodableArrayResultClosure<Id, O>) -> Void
+ handler: @escaping (T1, T2, @escaping IdentifierCodableArrayResultClosure<Id, O>) -> Void
) {
registerGetRoute(route: route, id: true, outputtype: O.self)
get(route) { request, response, next in
@@ -472,7 +472,7 @@ extension Router {
*/
public func get<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, IdentifierCodableArrayResultClosure<Id, O>) -> Void
+ handler: @escaping (T1, T2, T3, @escaping IdentifierCodableArrayResultClosure<Id, O>) -> Void
) {
registerGetRoute(route: route, id: true, outputtype: O.self)
get(route) { request, response, next in
@@ -798,7 +798,7 @@ extension Router {
*/
public func delete<T: TypeSafeMiddleware>(
_ route: String,
- handler: @escaping (T, ResultClosure) -> Void
+ handler: @escaping (T, @escaping ResultClosure) -> Void
) {
registerDeleteRoute(route: route, id: false)
delete(route) { request, response, next in
@@ -831,7 +831,7 @@ extension Router {
*/
public func delete<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware>(
_ route: String,
- handler: @escaping (T1, T2, ResultClosure) -> Void
+ handler: @escaping (T1, T2, @escaping ResultClosure) -> Void
) {
registerDeleteRoute(route: route, id: false)
delete(route) { request, response, next in
@@ -864,7 +864,7 @@ extension Router {
*/
public func delete<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware>(
_ route: String,
- handler: @escaping (T1, T2, T3, ResultClosure) -> Void
+ handler: @escaping (T1, T2, T3, @escaping ResultClosure) -> Void
) {
registerDeleteRoute(route: route, id: false)
delete(route) { request, response, next in
@@ -902,7 +902,7 @@ extension Router {
*/
public func delete<T: TypeSafeMiddleware, Id: Identifier>(
_ route: String,
- handler: @escaping (T, Id, ResultClosure) -> Void
+ handler: @escaping (T, Id, @escaping ResultClosure) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -942,7 +942,7 @@ extension Router {
*/
public func delete<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, Id: Identifier>(
_ route: String,
- handler: @escaping (T1, T2, Id, ResultClosure) -> Void
+ handler: @escaping (T1, T2, Id, @escaping ResultClosure) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -982,7 +982,7 @@ extension Router {
*/
public func delete<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, Id: Identifier>(
_ route: String,
- handler: @escaping (T1, T2, T3, Id, ResultClosure) -> Void
+ handler: @escaping (T1, T2, T3, Id, @escaping ResultClosure) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -1168,7 +1168,7 @@ extension Router {
*/
public func post<T: TypeSafeMiddleware, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T, I, @escaping CodableResultClosure<O>) -> Void
) {
registerPostRoute(route: route, id: false, inputtype: I.self, outputtype: O.self)
post(route) { request, response, next in
@@ -1209,7 +1209,7 @@ extension Router {
*/
public func post<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, I, @escaping CodableResultClosure<O>) -> Void
) {
registerPostRoute(route: route, id: false, inputtype: I.self, outputtype: O.self)
post(route) { request, response, next in
@@ -1250,7 +1250,7 @@ extension Router {
*/
public func post<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, T3, I, @escaping CodableResultClosure<O>) -> Void
) {
registerPostRoute(route: route, id: false, inputtype: I.self, outputtype: O.self)
post(route) { request, response, next in
@@ -1293,7 +1293,7 @@ extension Router {
*/
public func post<T: TypeSafeMiddleware, I: Codable, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T, I, IdentifierCodableResultClosure<Id, O>) -> Void
+ handler: @escaping (T, I, @escaping IdentifierCodableResultClosure<Id, O>) -> Void
) {
registerPostRoute(route: route, id: true, inputtype: I.self, outputtype: O.self)
post(route) { request, response, next in
@@ -1331,7 +1331,7 @@ extension Router {
*/
public func post<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, I: Codable, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, I, IdentifierCodableResultClosure<Id, O>) -> Void
+ handler: @escaping (T1, T2, I, @escaping IdentifierCodableResultClosure<Id, O>) -> Void
) {
registerPostRoute(route: route, id: true, inputtype: I.self, outputtype: O.self)
post(route) { request, response, next in
@@ -1369,7 +1369,7 @@ extension Router {
*/
public func post<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, I: Codable, Id: Identifier, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, I, IdentifierCodableResultClosure<Id, O>) -> Void
+ handler: @escaping (T1, T2, T3, I, @escaping IdentifierCodableResultClosure<Id, O>) -> Void
) {
registerPostRoute(route: route, id: true, inputtype: I.self, outputtype: O.self)
post(route) { request, response, next in
@@ -1410,7 +1410,7 @@ extension Router {
*/
public func put<T: TypeSafeMiddleware, Id: Identifier, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T, Id, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T, Id, I, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -1452,7 +1452,7 @@ extension Router {
*/
public func put<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, Id: Identifier, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, Id, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, Id, I, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -1494,7 +1494,7 @@ extension Router {
*/
public func put<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, Id: Identifier, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, Id, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, T3, Id, I, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -1546,7 +1546,7 @@ extension Router {
*/
public func patch<T: TypeSafeMiddleware, Id: Identifier, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T, Id, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T, Id, I, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -1594,7 +1594,7 @@ extension Router {
*/
public func patch<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, Id: Identifier, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, Id, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, Id, I, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return
@@ -1642,7 +1642,7 @@ extension Router {
*/
public func patch<T1: TypeSafeMiddleware, T2: TypeSafeMiddleware, T3: TypeSafeMiddleware, Id: Identifier, I: Codable, O: Codable>(
_ route: String,
- handler: @escaping (T1, T2, T3, Id, I, CodableResultClosure<O>) -> Void
+ handler: @escaping (T1, T2, T3, Id, I, @escaping CodableResultClosure<O>) -> Void
) {
if parameterIsPresent(in: route) {
return

original_message: fix: added escaping to result closure (#1292)


message: changed redirect test to local redirect

diff:
@@ -547,11 +547,11 @@ class TestResponse: KituraTest {
func testRedirect() {
performServerTest(router) { expectation in
- self.performRequest("get", path: "/redir", callback: {response in
+ self.performRequest("get", path: "/redirect", callback: {response in
XCTAssertNotNil(response, "ERROR!!! ClientRequest response object was nil")
do {
let body = try response?.readString()
- XCTAssertNotNil(body?.range(of: "ibm"), "response does not contain IBM")
+ XCTAssertEqual(body, "redirected to new route")
} catch {
XCTFail("Error reading body")
}
@@ -1198,9 +1198,9 @@ class TestResponse: KituraTest {
next()
}
- router.get("/redir") { _, response, next in
+ router.get("/redirect") { _, response, next in
do {
- try response.redirect("http://www.ibm.com")
+ try response.redirect("/redirected")
} catch {
XCTFail("Error sending response. Error=\(error.localizedDescription)")
}
@@ -1208,6 +1208,15 @@ class TestResponse: KituraTest {
next()
}
+ router.get("/redirected") { _, response, next in
+ do {
+ try response.send("redirected to new route").end()
+ } catch {
+ XCTFail("Error sending response. Error=\(error.localizedDescription)")
+ next()
+ }
+ }
+
// Error handling example
router.get("/error") { _, response, next in
response.status(HTTPStatusCode.internalServerError)

original_message: changed redirect test to local redirect (#1315)


message: Add MediaType struct

change_type: ADD

diff:
+/*
+ * Copyright IBM Corporation 2018
+ *
+ * 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.
+ */
+
+import KituraNet
+
+/**
+ The media type (formerly known as MIME type) is a standardized way to indicate the nature and format of a document.
+ This struct consists of a catagorical `topLevelType` and specific `subtype` seperated by a "/" (e.g. "text/plain").
+ In HTTP, The media type is sent as the first section of the "Content-Type" header and is case insensitive.
+ ### Usage Example: ###
+ ```swift
+ let mediaType = MediaType(type: .application, subtype: "json")
+ print(mediaType.description)
+ // Prints ("application/json")
+ ```
+ */
+public struct MediaType: CustomStringConvertible, Equatable, Hashable {
+
+ /// An enum of all media type's top level types.
+ public enum TopLevelType: String {
+ case application, audio, font, image, message, model, multipart, text, video
+ }
+
+ // MARK: Media type components
+
+ /// The topLevelType represents the category of the MediaType (e.g. "audio").
+ public let topLevelType: TopLevelType
+
+ /// The subtype is the specific MediaType (e.g. "html").
+ public let subType: String
+
+ // MARK: Initializers
+
+ /**
+ Initialize a `MediaType` instance with a `TopLevelType` type and `String` subtype. If no subtype is provided it will default to "*" representing all subtypes.
+ ### Usage Example: ###
+ ```swift
+ let mediaType = MediaType(type: .application, subtype: "json")
+ ```
+ */
+ public init(type: TopLevelType, subType: String = "*") {
+ self.topLevelType = type
+ self.subType = subType.lowercased()
+ self.description = "\(self.topLevelType)/\(self.subType)"
+ self.hashValue = description.hashValue
+ }
+
+ /**
+ Initialize a `MediaType` instance from a `String`. If no subtype is provided it will default to "*" representing all subtypes.
+ If the string preceding the first "/" is not a valid `TopLevelType` the initializer will return nil.
+ ### Usage Example: ###
+ ```swift
+ let mediaType = MediaType("application/json")
+ ```
+ */
+ public init? (_ mimeType: String) {
+ let mimeComponents = mimeType
+ .lowercased()
+ .components(separatedBy: "/")
+ guard let topLevelType = TopLevelType(rawValue: mimeComponents[0]) else {
+ return nil
+ }
+ self.topLevelType = topLevelType
+ if mimeComponents.indices.contains(1) && !mimeComponents[1].isEmpty {
+ self.subType = mimeComponents[1]
+ } else {
+ self.subType = "*"
+ }
+ self.description = "\(self.topLevelType)/\(self.subType)"
+ self.hashValue = description.hashValue
+ }
+
+ /**
+ Initialize a `MediaType` instance from a `KituraNet` `HeadersContainer`. This will extract the media type String from the first section of the "Content-type" header and use this to initialize itself. If the string preceding the first "/" is not a valid `TopLevelType` the initializer will return nil.
+ ### Usage Example: ###
+ ```swift
+ let headers = HeadersContainer()
+ headers.append("Content-Type", value: ["application/json"])
+ let mediaType = MediaType(headers: headers)
+ ```
+ */
+ init? (headers: HeadersContainer) {
+ let contentType = headers["Content-Type"]?[0]
+ guard let contentTypeComponents = contentType?.components(separatedBy: ";") else {
+ return nil
+ }
+ self.init(contentTypeComponents[0])
+ }
+
+ // MARK: Helper Constructors
+
+ /**
+ Helper constructor for the "application/json" media type
+ ### Usage Example: ###
+ ```swift
+ let mediaType = MediaType.json
+ print(mediaType.description)
+ // Prints ("application/json")
+ ```
+ */
+ public static let json = MediaType(type: .application, subType: "json")
+
+ /**
+ Helper constructor for the "application/x-www-form-urlencoded" media type
+ ### Usage Example: ###
+ ```swift
+ let mediaType = MediaType.urlEncoded
+ print(mediaType.description)
+ // Prints ("application/x-www-form-urlencoded")
+ ```
+ */
+ public static let urlEncoded = MediaType(type: .application, subType: "x-www-form-urlencoded")
+
+ // MARK: Protocol conformance
+
+ /**
+ Returns the media type, as a String structured: `topLevelType`/`subtype`. Required for CustomStringConvertible conformance.
+ ### Usage Example: ###
+ ```swift
+ print(mediaType.description)
+ // Prints ("application/json")
+ ```
+ */
+ public let description: String
+
+ /// Compares two MediaTypes returning true if they are equal. Required for Equatable conformance.
+ public static func == (lhs: MediaType, rhs: MediaType) -> Bool {
+ return lhs.description == rhs.description
+ }
+
+ /// The hashValue for the MediaTypes. Required for Hashable conformance.
+ public let hashValue: Int
+}
+

diff:
+/**
+ * Copyright IBM Corporation 2018
+ *
+ * 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.
+ **/
+
+import Foundation
+import XCTest
+
+@testable import Kitura
+@testable import KituraNet
+
+class TestMediaType: KituraTest {
+
+ static var allTests: [(String, (TestMediaType) -> () throws -> Void)] {
+ return [
+ ("testAllTextMediaTypeBuilder", testAllTextMediaTypeBuilder),
+ ("testAllTextSlashMediaTypeBuilder", testAllTextSlashMediaTypeBuilder),
+ ("testHTMLMediaTypeBuilder", testPartsHTMLMediaTypeBuilder),
+ ("testMediaCaseInsensitive", testMediaCaseInsensitive),
+ ("testPartsAllTextMediaTypeBuilder", testPartsAllTextMediaTypeBuilder),
+ ("testPartsHTMLMediaTypeBuilder", testPartsHTMLMediaTypeBuilder),
+ ("testPartsMediaCaseInsensitive", testPartsMediaCaseInsensitive),
+ ("testNoHeaderMediaType", testNoHeaderMediaType),
+ ("testSingleHeaderMediaType", testSingleHeaderMediaType),
+ ("testTwoHeaderMediaType", testTwoHeaderMediaType),
+ ("testWrongHeaderMediaType", testWrongHeaderMediaType),
+ ]
+ }
+
+ func testAllTextMediaTypeBuilder() {
+ let textMediaType = MediaType("text")
+ XCTAssertEqual(textMediaType?.description, "text/*")
+ XCTAssertEqual(textMediaType?.topLevelType, .text)
+ XCTAssertEqual(textMediaType?.subType, "*")
+ }
+
+ func testAllTextSlashMediaTypeBuilder() {
+ let textMediaType = MediaType("text/")
+ XCTAssertEqual(textMediaType?.description, "text/*")
+ XCTAssertEqual(textMediaType?.topLevelType, .text)
+ XCTAssertEqual(textMediaType?.subType, "*")
+ }
+
+ func testHTMLMediaTypeBuilder() {
+ let textMediaType = MediaType("text/html")
+ XCTAssertEqual(textMediaType?.description, "text/html")
+ XCTAssertEqual(textMediaType?.topLevelType, .text)
+ XCTAssertEqual(textMediaType?.subType, "html")
+ }
+
+ func testMediaCaseInsensitive() {
+ let textMediaType = MediaType("TexT/HTml")
+ XCTAssertEqual(textMediaType?.description, "text/html")
+ XCTAssertEqual(textMediaType?.topLevelType, .text)
+ XCTAssertEqual(textMediaType?.subType, "html")
+ }
+
+ func testIncorrectTopLevelType() {
+ XCTAssertNil(MediaType("Wrong/text"))
+ }
+
+ func testPartsAllTextMediaTypeBuilder() {
+ let textMediaType = MediaType(type: .text)
+ XCTAssertEqual(textMediaType.description, "text/*")
+ XCTAssertEqual(textMediaType.topLevelType, .text)
+ XCTAssertEqual(textMediaType.subType, "*")
+ }
+
+ func testPartsHTMLMediaTypeBuilder() {
+ let textMediaType = MediaType(type: .text, subType: "html")
+ XCTAssertEqual(textMediaType.description, "text/html")
+ XCTAssertEqual(textMediaType.topLevelType, .text)
+ XCTAssertEqual(textMediaType.subType, "html")
+ }
+
+ func testPartsMediaCaseInsensitive() {
+ let textMediaType = MediaType(type: .text , subType: "hTmL")
+ XCTAssertEqual(textMediaType.description, "text/html")
+ XCTAssertEqual(textMediaType.topLevelType, .text)
+ XCTAssertEqual(textMediaType.subType, "html")
+ }
+
+ func testNoHeaderMediaType() {
+ let headers = HeadersContainer()
+ XCTAssertNil(MediaType(headers: headers))
+ }
+
+ func testSingleHeaderMediaType() {
+ let headers = HeadersContainer()
+ headers.append("Content-Type", value: ["text/html"])
+ let textMediaType = MediaType(headers: headers)
+ XCTAssertEqual(textMediaType?.description, "text/html")
+ XCTAssertEqual(textMediaType?.topLevelType, .text)
+ XCTAssertEqual(textMediaType?.subType, "html")
+ }
+
+ func testTwoHeaderMediaType() {
+ let headers = HeadersContainer()
+ headers.append("Content-Type", value: ["text/html", "application/json"])
+ let textMediaType = MediaType(headers: headers)
+ XCTAssertEqual(textMediaType?.description, "text/html")
+ XCTAssertEqual(textMediaType?.topLevelType, .text)
+ XCTAssertEqual(textMediaType?.subType, "html")
+ }
+
+ func testWrongHeaderMediaType() {
+ let headers = HeadersContainer()
+ headers.append("Content-Type", value: "incorrect/html")
+ XCTAssertNil(MediaType(headers: headers))
+ }
+}

diff:
@@ -63,5 +63,6 @@ XCTMain([
testCase(TestBridgingHTTPStatusCode.allTests.shuffled()),
testCase(TestBridgingRequestError.allTests.shuffled()),
testCase(TestSwaggerGeneration.allTests.shuffled()),
+ testCase(TestMediaType.allTests.shuffled()),
// testCase(TestCRUDTypeRouter.allTests.shuffled()),
].shuffled())

original_message: Add MediaType struct (#1314)


diff:
@@ -39,6 +39,8 @@ class TestCustomCoders: KituraTest {
}
}
+ let dateFormatter = DateFormatter()
+
func testCustomCoder() {
let jsonEncoder: () -> BodyEncoder = {
let encoder = JSONEncoder()
@@ -152,8 +154,14 @@ class TestCustomCoders: KituraTest {
performServerTest(customRouter) { expectation in
self.performRequest("get", path: "/rawget", callback: { response in
- if let response = response, let responseString = try? response.readString() {
- XCTAssertEqual(responseString, "&date=2018-02-21T09:47:36%2B0000")
+ if let response = response,
+ let responseString = try? response.readString(),
+ let unwrappedString = responseString
+ {
+ // Drop first 6 characters from response String to remove "&date=" and just leave the date String.
+ let responseDate = self.dateFormatter.date(from: String((unwrappedString.dropFirst(6))))
+ let expectedDate = self.dateFormatter.date(from: "2018-02-21T09:47:36%2B0000")
+ XCTAssertEqual(responseDate, expectedDate)
} else {
XCTFail("Unable to read response string")
}
@@ -176,8 +184,14 @@ class TestCustomCoders: KituraTest {
performServerTest(customRouter) { expectation in
self.performRequest("get", path: "/rawget", callback: { response in
- if let response = response, let responseString = try? response.readString() {
- XCTAssertEqual(responseString, "&date=2018-02-21T09:47:36%2B0000")
+ if let response = response,
+ let responseString = try? response.readString(),
+ let unwrappedString = responseString
+ {
+ // Drop first 6 characters from response String to remove "&date=" and just leave the date String.
+ let responseDate = self.dateFormatter.date(from: String((unwrappedString.dropFirst(6))))
+ let expectedDate = self.dateFormatter.date(from: "2018-02-21T09:47:36%2B0000")
+ XCTAssertEqual(responseDate, expectedDate)
} else {
XCTFail("Unable to read response string")
}

original_message: Test: CustomCoder test compares dates instead of Strings (#1349)


message: Fix: Add locking for Kitura servers

diff:
@@ -69,7 +69,9 @@ public class Kitura {
server.sslConfig = sslConfig?.config
server.keepAliveState = keepAliveState
server.allowPortReuse = allowPortReuse
+ serverLock.lock()
httpServersAndPorts.append((server: server, port: port))
+ serverLock.unlock()
return server
}
@@ -94,7 +96,9 @@ public class Kitura {
let server = FastCGI.createServer()
server.delegate = delegate
server.allowPortReuse = allowPortReuse
+ serverLock.lock()
fastCGIServersAndPorts.append((server: server, port: port))
+ serverLock.unlock()
return server
}
@@ -126,6 +130,7 @@ public class Kitura {
/// Kitura.start()
///```
public class func start() {
+ serverLock.lock()
for (server, port) in httpServersAndPorts {
Log.verbose("Starting an HTTP Server on port \(port)...")
do {
@@ -142,6 +147,7 @@ public class Kitura {
Log.error("Error listening on port \(port): \(error). Use server.failed(callback:) to handle")
}
}
+ serverLock.unlock()
}
// MARK: Stop Servers
@@ -159,6 +165,7 @@ public class Kitura {
///
/// - Parameter unregister: If servers should be unregistered after they are stopped (default true).
public class func stop(unregister: Bool = true) {
+ serverLock.lock()
for (server, port) in httpServersAndPorts {
Log.verbose("Stopping HTTP Server on port \(port)...")
server.stop()
@@ -173,9 +180,11 @@ public class Kitura {
httpServersAndPorts.removeAll()
fastCGIServersAndPorts.removeAll()
}
+ serverLock.unlock()
}
typealias Port = Int
+ private static let serverLock = NSLock()
internal private(set) static var httpServersAndPorts = [(server: HTTPServer, port: Port)]()
internal private(set) static var fastCGIServersAndPorts = [(server: FastCGIServer, port: Port)]()
}

original_message: Fix: Add locking for Kitura servers (#1377)


message: fix: Remove hashvalue for swift >= 4.2

diff:
@@ -27,7 +27,7 @@ import KituraNet
// Prints ("application/json")
```
*/
-public struct MediaType: CustomStringConvertible, Equatable, Hashable {
+public struct MediaType: CustomStringConvertible {
/// An enum of all recognized top-level media types (categories). See:
/// https://www.iana.org/assignments/media-types/media-types.xhtml
@@ -73,7 +73,6 @@ public struct MediaType: CustomStringConvertible, Equatable, Hashable {
self.topLevelType = type
self.subType = subType.lowercased()
self.description = "\(self.topLevelType)/\(self.subType)"
- self.hashValue = description.hashValue
}
/**
@@ -100,7 +99,6 @@ public struct MediaType: CustomStringConvertible, Equatable, Hashable {
}
//self.description = "\(self.topLevelType)/\(self.subType)"
self.description = self.topLevelType.rawValue.appending("/").appending(self.subType)
- self.hashValue = description.hashValue
}
/**
@@ -154,12 +152,23 @@ public struct MediaType: CustomStringConvertible, Equatable, Hashable {
*/
public let description: String
+
+}
+
+extension MediaType: Equatable {
+ #if !swift(>=4.2)
/// Compares two MediaTypes returning true if they are equal. Required for Equatable conformance.
public static func == (lhs: MediaType, rhs: MediaType) -> Bool {
return lhs.description == rhs.description
}
-
- /// The hashValue for the MediaTypes. Required for Hashable conformance.
- public let hashValue: Int
+ #endif
}
+extension MediaType: Hashable {
+ #if !swift(>=4.2)
+ // A unique integer produced by hashing the MediaType. Required for Hashable conformance.
+ public var hashValue: Int {
+ return description.hashValue
+ }
+ #endif
+}

original_message: fix: Remove hashvalue for swift >= 4.2 (#1400)


message: Fix line height text wrapping issue

diff:
@@ -164,6 +164,7 @@ export class PreviewModal extends Component {
<CodeMirror
options={{
lineNumbers: true,
+ lineWrapping: true,
tabSize: 4,
readOnly: false,
mode: getHighlightMode(this.props.dialect)

license: MIT License

repo: plotly/falcon

original_message: Fix line height text wrapping issue


message: Fix data grid horizontal overflow

diff:
border-color: #c8d4e3 !important;
}
+/* fix horizontal overflow issues */
+.react-grid-Cell {
+ box-sizing: border-box;
+}
+
+
.react-grid-Header {
box-shadow: unset !important;
max-height: 64px;

diff:
background-color: #f9f9f9;
}
+/* fix horizontal overflow issues */
+.scheduler-table .react-grid-Cell {
+ box-sizing: border-box;
+}
+
.scheduler-table .react-grid-Row {
border-top: 1px solid #c8d4e3;
cursor: pointer;

diff:
@@ -270,7 +270,7 @@ class Scheduler extends Component {
</Row>
</Column>
</Row>
- <Row className="scheduler-table" style={{padding: '0 16px 16px', width: 'auto'}}>
+ <Row className="scheduler-table" style={{margin: '0 16px 16px', width: 'auto'}}>
<ReactDataGrid
onRowClick={this.openPreview}
columns={this.columns}

original_message: Fix data grid horizontal overflow


message: Remove react-grid-cell cell border

diff:
/* fix horizontal overflow issues */
.react-grid-Cell {
box-sizing: border-box;
+ border-right: none !important;
}

diff:
/* fix horizontal overflow issues */
.scheduler-table .react-grid-Cell {
box-sizing: border-box;
+ border-right: none;
}
.scheduler-table .react-grid-Row {

original_message: Remove react-grid-cell cell border