userver: Non-Coroutine Console Tool
Loading...
Searching...
No Matches
Non-Coroutine Console Tool

Before you start

Make sure that you can compile and run framework tests as described at Configure, Build and Install.

Step by step guide

The userver framework allows to use it's non-coroutine parts by using the userver-universal CMake target. It provides usefull utilities like utils::FastPimpl, utils::TrivialBiMap, JSON and YAML formats, utils::AnyMovable, cache::LruMap and many other utilities. See Universal for a list of available functions and classes.

Let's write a simple JSON to YAML converter with the help of userver-universal.

Code

The implementation is quite straightforward. Include the necessary C++ Standard library and userver headers:

#include <iostream>
#include <string>

Write the logic for converting each of the JSON types to YAML type:

USERVER_NAMESPACE_BEGIN
namespace formats::parse {
yaml::Value Convert(const json::Value& json, parse::To<yaml::Value>) {
yaml::ValueBuilder yaml_vb;
if (json.IsBool()) {
yaml_vb = json.ConvertTo<bool>();
} else if (json.IsInt64()) {
yaml_vb = json.ConvertTo<int64_t>();
} else if (json.IsUInt64()) {
yaml_vb = json.ConvertTo<uint64_t>();
} else if (json.IsDouble()) {
yaml_vb = json.ConvertTo<double>();
} else if (json.IsString()) {
yaml_vb = json.ConvertTo<std::string>();
} else if (json.IsArray()) {
yaml_vb = {common::Type::kArray};
for (const auto& elem : json) {
yaml_vb.PushBack(elem.ConvertTo<yaml::Value>());
}
} else if (json.IsObject()) {
yaml_vb = {common::Type::kObject};
for (auto it = json.begin(); it != json.end(); ++it) {
yaml_vb[it.GetName()] = it->ConvertTo<yaml::Value>();
}
}
return yaml_vb.ExtractValue();
}
} // namespace formats::parse
USERVER_NAMESPACE_END

Finally, read data from std::cin, parse it as JSON, convert to YAML and output it as text:

int main() {
namespace formats = USERVER_NAMESPACE::formats;
auto json = formats::json::FromStream(std::cin);
formats::yaml::Serialize(json.ConvertTo<formats::yaml::Value>(), std::cout);
std::cout << std::endl;
}

Build and Run

To build the sample, execute the following build steps at the userver root directory:

mkdir build_release
cd build_release
cmake -DCMAKE_BUILD_TYPE=Release ..
make userver-samples-json2yaml

After that a tool is compiled an it could be used:

bash
$ samples/json2yaml/userver-samples-json2yaml
{"key": {"nested-key": [1, 2.0, "hello!", {"key-again": 42}]}}
^D
key:
nested-key:
- 1
- 2
- hello!
- key-again: 42

Testing

The code could be tested using any of the unit-testing frameworks, like Boost.Test, GTest, and so forth.

However, to test the code with pytest some additional setup should be done:

  • Inform CMake about the test and that it should be started by Python. Pass the path to the CMake built binary to Python:
    add_test(NAME "${PROJECT_NAME}-pytest"
    COMMAND
    "${TESTSUITE_PYTHON_BINARY}" -m pytest
    "--test-binary=${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}"
    WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
    )
  • Add a fixture to conftest.py to get the path to the CMake built binary:
    import pytest
    def pytest_addoption(parser) -> None:
    group = parser.getgroup('userver')
    group.addoption('--test-binary', type=str, help='Path to build utility.')
    @pytest.fixture(scope='session')
    def path_to_json2yaml(pytestconfig):
    return pytestconfig.option.test_binary
  • Write the test:
    import subprocess
    _EXPECTED_OUTPUT = """key:
    nested-key:
    - 1
    - 2
    - hello!
    - key-again: 42
    """
    def test_basic(path_to_json2yaml):
    pipe = subprocess.Popen(
    [path_to_json2yaml],
    stdout=subprocess.PIPE,
    stderr=subprocess.PIPE,
    stdin=subprocess.PIPE,
    )
    stdout, stderr = pipe.communicate(
    input=b'{"key":{"nested-key": [1, 2.0, "hello!", {"key-again": 42}]}}',
    )
    assert stdout.decode('utf-8') == _EXPECTED_OUTPUT, stderr

Full sources

See the full example at: