Search code examples
c++enumsoat++

What is best way to print enums in Oat++?


I want to make an article about Oat++ framework for students. Unfortunately I have a problem with printing an enum value. I was experimenting with code and currently I found a way:

std::cout << "Genre: " << book->genre.getEntryByValue(*book->genre).name.std_str() << std::endl;

But I think that this is not a professional way. Unfortunately coda is templated-define-driven so it is really hard to find out something from code.

Here is the full client code:

////////// file: ClientComponent.hpp
#include <oatpp/parser/json/mapping/ObjectMapper.hpp>
#include <oatpp-libressl/Config.hpp>
#include <oatpp-libressl/client/ConnectionProvider.hpp>
#include <oatpp/core/macro/component.hpp>

constexpr const int listeningPortHttps = 8443;
constexpr const char listeningAddress[] = "localhost";


class ClientComponent
{
public:
    OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ClientConnectionProvider>, sslClientConnectionProvider)([] {
        auto config = oatpp::libressl::Config::createShared();
        tls_config_insecure_noverifycert(config->getTLSConfig());
        tls_config_insecure_noverifyname(config->getTLSConfig());
        return oatpp::libressl::client::ConnectionProvider::createShared(config, {listeningAddress, listeningPortHttps});
    }());
};


////////// file: BooksDto.hpp:
#include <oatpp/core/macro/codegen.hpp>
#include <oatpp-swagger/Controller.hpp>


#include OATPP_CODEGEN_BEGIN(DTO)

ENUM(BookGenre, v_int32,
     VALUE(UNKNOWN, 0, "unkown book genre"),
     VALUE(FICTION, 1, "fiction"),
     VALUE(NONFICTION, 1, "non-fiction"),
     VALUE(FANTASY, 1, "fantasy"),
     VALUE(BIOGRAPHY, 1, "biography"),
     VALUE(SCIFI, 1, "scifi")
     );


class BookDto : public oatpp::DTO
{
    DTO_INIT(BookDto, DTO /* Extends */)

    DTO_FIELD(UInt32, id);
    DTO_FIELD(String, title, "title");
    DTO_FIELD(String, author, "author");

    DTO_FIELD(Enum<BookGenre>::AsString, genre, "genre");
};

#include OATPP_CODEGEN_END(DTO)


////////// file: Client.hpp
// #pragma once

#include <oatpp/web/client/ApiClient.hpp>

#include OATPP_CODEGEN_BEGIN(ApiClient) //<- Begin Codegen

class BookClient : public oatpp::web::client::ApiClient
{
public:
    API_CLIENT_INIT(BookClient)

    API_CALL("POST", "/books", createBook, BODY_DTO(Object<BookDto>, bookDto), HEADER(String, authorization, "Authorization"))
    API_CALL("GET", "/books", getBooks)
    API_CALL("GET", "/books/{id}", getBookById, PATH(UInt64, id))
    API_CALL("PUT", "/books/{id}", updateBook, PATH(UInt64, id), BODY_DTO(Object<BookDto>, bookDto), HEADER(String, authorization, "Authorization"))
    API_CALL("DELETE", "/books/{id}", deleteBook, PATH(UInt64, id), HEADER(String, authorization, "Authorization"))
};

#include OATPP_CODEGEN_END(ApiClient) //<- End Codegen


////////// file: main.cpp
#include <iostream>
#include <oatpp/network/tcp/client/ConnectionProvider.hpp>
#include <oatpp/web/client/HttpRequestExecutor.hpp>

constexpr const char* USER = "my_user";
constexpr const char* PASSWORD = "my-secret-password";


void addBook(std::string_view basicAuthHeader, const char* title, const char* author, BookGenre genre, auto& bookApiClient);

void printBooks(auto& bookApiClient, auto& objectMapper);

void updateBook(std::string_view basicAuthHeader, v_uint64 id, const char* title, const char* author, BookGenre genre, auto& bookApiClient);

void deleteBook(std::string_view basicAuthHeader, v_uint64 id, auto& bookApiClient);

void printBookById(v_uint64 id, auto& bookApiClient, auto& objectMapper);

void runClient();

std::string base64_encode(const std::string &in); // ignore this function

std::string createBasicAuthHeader(const std::string& user, const std::string& password)
{
    return "Basic " + base64_encode(user + ":" + password);
}

///////////////// main()
int main()
{
    oatpp::base::Environment::init();
    runClient();

    oatpp::base::Environment::destroy();
}


void runClient()
{
    std::shared_ptr<oatpp::web::client::HttpRequestExecutor> httpRequestExecutor;

    constexpr bool httpsConnection = true;
    if constexpr(httpsConnection)
    {
        ClientComponent clientComponent;
        OATPP_COMPONENT(std::shared_ptr<oatpp::network::ClientConnectionProvider>, sslClientConnectionProvider);
        httpRequestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(sslClientConnectionProvider);
    }
    else
    {
        /// for HTTP:
        auto connectionProvider = oatpp::network::tcp::client::ConnectionProvider::createShared({"localhost", 8000, oatpp::network::Address::IP_4});
        httpRequestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(connectionProvider);
    }

    auto jsonObjectMapper = oatpp::parser::json::mapping::ObjectMapper::createShared();

    auto bookApiClient = BookClient::createShared(httpRequestExecutor, jsonObjectMapper);

    std::string basicAuthHeader = createBasicAuthHeader(USER, PASSWORD);

    addBook(basicAuthHeader, "Opus magnum C++11", "Jerzy Grebosz", BookGenre::FICTION, bookApiClient);
    addBook(basicAuthHeader, "Skuteczny nowoczesny C++. 42 sposoby lepszego poslugiwania się jezykami C++11 i C++14", "Scott Meyers", BookGenre::NONFICTION, bookApiClient);
    addBook(basicAuthHeader, "C++17 STL. Receptury", "Jacek Galowicz", BookGenre::NONFICTION, bookApiClient);

    printBooks(bookApiClient, jsonObjectMapper);

    std::cout << std::endl;

    updateBook(basicAuthHeader, 1, "Misja w nadprzestrzen C++14/17", "Jerzy Grebosz", BookGenre::FANTASY, bookApiClient);

    printBookById(1, bookApiClient, jsonObjectMapper);

    deleteBook(basicAuthHeader, 1, bookApiClient);

    printBooks(bookApiClient, jsonObjectMapper);


    auto rawData = bookApiClient->getBooks()->readBodyToString();
    OATPP_LOGD(BookClient::TAG, "[doGet] data='%s'", rawData->c_str());
}

void addBook(std::string_view basicAuthHeader, const char* title, const char* author, BookGenre genre, auto& bookApiClient)
{
    auto book = BookDto::createShared();
    book->title = title;
    book->author = author;
    book->genre = genre;

    auto response = bookApiClient->createBook(book, basicAuthHeader.data());

    const BookClient::Status status{response->getStatusCode(), "creation book error"};
    OATPP_ASSERT_HTTP(response->getStatusCode() == 200, status, std::string("Failed to create book: ") + title);
}

void printBooks(auto& bookApiClient, auto& objectMapper)
{
    auto response = bookApiClient->getBooks();

    const BookClient::Status status{response->getStatusCode(), "getting books error"};
    OATPP_ASSERT_HTTP(response->getStatusCode() == 200, status, "Failed to get books");

    auto books = response->template readBodyToDto<oatpp::List<oatpp::Object<BookDto>>>(objectMapper);
    for (const auto& book : *books)
    {
        std::cout << "Book ID: " << book->id << std::endl;
        std::cout << "Title: " << book->title->c_str() << std::endl;
        std::cout << "Author: " << book->author->c_str() << std::endl;
        std::cout << "Genre: " << book->genre.getEntryByValue(*book->genre).name.std_str() << std::endl;
        std::cout << "--------" << std::endl;
    }
}

void updateBook(std::string_view basicAuthHeader, v_uint64 id, const char* title, const char* author, BookGenre genre, auto& bookApiClient)
{
    auto book = BookDto::createShared();
    book->title = title;
    book->author = author;
    book->genre = genre;

    auto response = bookApiClient->updateBook(id, book, basicAuthHeader.data());
    const BookClient::Status status{response->getStatusCode(), "updating book error"};
    OATPP_ASSERT_HTTP(response->getStatusCode() == 200, status, std::string("Failed to update book: ") + title);
}

void deleteBook(std::string_view basicAuthHeader, v_uint64 id, auto& bookApiClient)
{
    auto response = bookApiClient->deleteBook(id, basicAuthHeader.data());
    const BookClient::Status status{response->getStatusCode(), "deleting book error"};
    OATPP_ASSERT_HTTP(response->getStatusCode() == 200, status, std::string("Failed to delete book with ID: ") + std::to_string(id));
}

void printBookById(v_uint64 id, auto& bookApiClient, auto& objectMapper)
{
    auto response = bookApiClient->getBookById(id);
    const BookClient::Status status{response->getStatusCode(), "getting book by id error"};
    OATPP_ASSERT_HTTP(response->getStatusCode() == 200, status, "Failed to get book by ID");

    auto book = response->template readBodyToDto<oatpp::Object<BookDto>>(objectMapper);
    std::cout << "Book ID: " << book->id << std::endl;
    std::cout << "Title: " << book->title->c_str() << std::endl;
    std::cout << "Author: " << book->author->c_str() << std::endl;
    std::cout << "Genre: " << book->genre.getEntryByValue(*book->genre).name.std_str() << std::endl;
    std::cout << "--------" << std::endl;
}


std::string base64_encode(const std::string &in) // ignore this function
{
    std::string out;

    int val = 0, valb = -6;
    for (unsigned char c : in)
    {
        val = (val << 8) + c;
        valb += 8;
        while (valb >= 0)
        {
            out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[(val >> valb) & 0x3F]);
            valb -= 6;
        }
    }
    if (valb > -6)
        out.push_back("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[((val << 8) >> (valb + 8)) & 0x3F]);
    while (out.size() % 4)
        out.push_back('=');
    return out;
}

Solution

  • getEntryByValye() is a static method of oatpp::Enum so you don't need an object instance to use it:

    auto enumEntry = oatpp::Enum<BookGenre>::getEntryByValue(BookGenre::NONFICTION);
    OATPP_LOGd("TEST-ENUM", "name: '{}'", enumEntry.name.toString())
    
    

    Here is a complete example to play with (Oat++ 1.4.0):

    #include "oatpp/Types.hpp"
    #include "oatpp/base/Log.hpp"
    #include "oatpp/macro/codegen.hpp"
    
    #include OATPP_CODEGEN_BEGIN(DTO)
    
    ENUM(BookGenre, v_int32,
    
    //        |  name    | int-value |  qualifier  | description
    
         VALUE(UNKNOWN,    0,         "unknown",     "unkown book genre"),
         VALUE(FICTION,    1,         "fiction",     "fiction book genre"),
         VALUE(NONFICTION, 2,         "non_fiction", "non-fiction book genre"),
         VALUE(FANTASY,    3,         "fantasy",     "fantasy book genre"),
         VALUE(BIOGRAPHY,  4,         "biography",   "biography book genre"),
         VALUE(SCIFI,      5,         "scifi",       "scifi book genre")
    );
    
    #include OATPP_CODEGEN_END(DTO)
    
    void runTests() {
    
      auto enumEntry = oatpp::Enum<BookGenre>::getEntryByValue(BookGenre::NONFICTION);
    
      OATPP_LOGd("TEST-ENUM", "unqualifiedName: '{}', name: '{}', description: '{}'",
                 enumEntry.unqualifiedName.toString(), enumEntry.name.toString(), enumEntry.description.toString())
    
    }
    
    int main() {
    
      oatpp::Environment::init();
      runTests();
      oatpp::Environment::destroy();
    
      return 0;
    
    }