oatpp/changelog/1.1.0.md
Ferry Huberts aa02f4bb3f Fix Clang compiler warnings (-Wextra-semi-stmt)
Signed-off-by: Ferry Huberts <ferry.huberts@pelagic.nl>
2023-09-02 23:12:26 +02:00

8.0 KiB

Oat++ 1.1.0

Oat++ 1.1.0 is introducing breaking changes. Please read carefully to prepare for migration.

Feel free to ask questions - Chat on Gitter

Contents:

No More ObjectWrapper

ObjectWrapper is a base-class for oatpp core types (oatpp/core/Types.hpp). The explicit ::ObjectWrapper qualifier is removed.

DTO:

class MyDto : oatpp::DTO { // <--- Notice the 'oatpp::DTO' now. NOT the 'oatpp::Object'

  DTO_INIT(MyDto, DTO)
  
  DTO_FIELD(Object<MyDto>, nested);             // <--- Notice the oatpp::Object<T> for Objects, instead of T::ObjectWrapper
  DTO_FIELD(List<Any>, listOfAny);              // <--- No '::ObjectWrapper' for collections.
  DTO_FIELD(Fields<List<String>>, mapOfLists);

}

ApiController:

ENDPOINT("POST", "body-dto", postWithBody,
         BODY_DTO(Object<MyDto>, body)) {
  ...    
}

ApiClient:

API_CALL("POST", "body-dto", postWithBody, BODY_DTO(Object<MyDto>, body))

In Other Code-Blocks

oatpp::Object<MyDto> myDto;
oatpp::List<oatpp::Object<MyDto>> listOfDtos;

...

auto myDto = objectMapper->readFromString<Object<MyDto>>(json);
auto listOfDtos = objectMapper->readFromString<List<Object<MyDto>>>(json);

Object-Mapping Simplified Primitives

No more <primitive>->getValue().

oatpp::Int32 objV = 32;
v_int32 v = objV; // You may check for nullptr before doing this

bool equals = v == objV; // <--- NO NEED to check for nullptr here
bool isNull = objV == nullptr;
bool isNull = !objV;

Object-Mapping std Collections

Now oatpp::<mapping-enabled-collections> are based on std::<collections>

Example:

oatpp::Vector<oatpp::String> vector({});
oatpp::List<oatpp::String> list({});
oatpp::UnorderedSet<oatpp::String> set({});
oatpp::Fields<oatpp::String> pairList({});
oatpp::UnorderedFields<oatpp::String> hashMap({});

oatpp::Vector<oatpp::String> vector = {"a", "b", "c"};
oatpp::List<oatpp::String> list = {"a", "b", "c"};
oatpp::UnorderedSet<oatpp::String> set = {"a", "b", "c"};
oatpp::Fields<oatpp::String> pairList = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}};
oatpp::UnorderedFields<oatpp::String> hashMap = {{"k1", "v1"}, {"k2", "v2"}, {"k3", "v3"}};

vector[0] = "z"; // <--- Complexity = O(1);
vector->push_back("www");

list[0] = "z"; // <--- Complexity = O(n);
list->push_back("www");

bool contains = set["b"]; // <--- Complexity = O(1);
set->insert("z")

auto value = pairList.getValueByKey("k1"); // <--- Complexity = O(n); // note '.' here, not '->' !!!
pairList->push_back({"key_z", "z"}); 

hashMap["k1"] = "z" // <--- Complexity = O(1);
hashMap->insert({"key_z", "z"});

for(auto& item : *vector) {
  ...
}

for(auto& item : *list) {
  ...
}

for(auto& item : *set) {
  ...
}

for(auto& pair : *pairList) {
  ...
}

for(auto& pair : *hashMap) {
  ...
}

Type oatpp::Any

The new Type Introduced - oatpp::Any.

Now it's possible to do like this:

class MyDto : public oatpp::DTO {

  DTO_INIT(MyDto, DTO)
 
  DTO_FIELD(Any, any); // Put any oatpp::<Type> here
  DTO_FIELD(List<Any>, listOfAny)
  DTO_FIELD(Fields<Any>, mapOfAny);

};

JSON Serializer

Will serialize Any depending on the value type it stores.

JSON Deserializer

Will try to guess the type of the Any. Currently, Any is deserialized as follows:

  • JSON objects are deserialized to Any holding oatpp::Fields<Any>.
  • JSON lists are deserialized to Any holding oatpp::List<Any>.
  • JSON null is deserialized to Any holding nullptr.
  • JSON true/false is deserialized to Any holding oatpp::Boolean.
  • JSON number is deserialized to Any holding oatpp::Float64.

Example

oatpp::Fields<oatpp::Any> map = {
  {"title", oatpp::String("Hello Any!")},
  {"listOfAny",
   oatpp::List<oatpp::Any>({
     oatpp::Int32(32),
     oatpp::Float32(0.32),
     oatpp::Boolean(true)
   })
  }
};

auto json = mapper->writeToString(map); 

Output:

{
  "title": "Hello Any!",
  "listOfAny": [
    32,
    0.3199999928474426,
    true
  ]
}

Object-Mapping Enum

Enum is added to DTO codegen.

Declaration

#include OATPP_CODEGEN_BEGIN(DTO)

ENUM(Enum1, v_int32,            // <-- type is mandatory 
     VALUE(V_1, 1),             // <-- integral value is mandatory
     VALUE(V_2, 2, "name-2"),
     VALUE(V_3, 3, "name-3", "description_3")
)

#include OATPP_CODEGEN_END(DTO)

This will generate:

enum class Enum1 : v_int32 {
  V_1 = 1,
  V_2 = 2,
  V_3 = 3
}

... // PLUS Meta here

Current limitation - ENUM can not be declared nested in the class :(. This may be fixed later.

Usage

DTO

ENUM(MyEnum, v_int32,
  VALUE(V1, 10, "enum1-v1"),
  VALUE(V2, 20, "enum1-v2"),
  VALUE(V3, 30, "enum1-v3")
);

class MyDto : public oatpp::DTO {

  DTO_INIT(MyDto, DTO)

  DTO_FIELD(Enum<MyEnum>, enum1);            // Default interpretation - AsString
  DTO_FIELD(Enum<MyEnum>::NotNull, enum2);   // NOT_NULL constraint for Ser/De

  DTO_FIELD(Enum<MyEnum>::AsString, enum3);  // Ser/De as string name
  DTO_FIELD(Enum<MyEnum>::AsNumber, enum4);  // Ser/De as a corresponding integral value

  DTO_FIELD(Enum<MyEnum>::AsString::NotNull, enum5);
  DTO_FIELD(Enum<MyEnum>::AsNumber::NotNull, enum6);

};

ApiController

  ENDPOINT("GET", "enum/as-string", testEnumString,
           HEADER(Enum<AllowedHeaderValues>::AsString, enumValue, "X-MyEnum"))
  {
    return createResponse(Status::CODE_200, "OK");
  }

  ENDPOINT("GET", "enum/as-number", testEnumNumber,
           HEADER(Enum<AllowedHeaderValues>::AsNumber, enumValue, "X-MyEnum"))
  {
    return createResponse(Status::CODE_200, "OK");
  }

ApiClient

  API_CALL("GET", "enum/as-string", getWithEnumHeaderAsString, HEADER(Enum<AllowedHeaderValues>::AsString, enumValue, "X-MyEnum"))
  API_CALL("GET", "enum/as-number", getWithEnumHeaderAsNumber, HEADER(Enum<AllowedHeaderValues>::AsNumber, enumValue, "X-MyEnum"))

Meta functions

{
  auto entry = oatpp::Enum<MyEnum>::getEntryByName("<name>");
  auto entry = oatpp::Enum<MyEnum>::getEntryByValue(MyEnum::VALUE);
  auto entry = oatpp::Enum<MyEnum>::getEntryByUnderlyingValue(123);
  auto entry = oatpp::Enum<MyEnum>::getEntryByIndex(0);
  ...
  OATPP_LOGD("Entry", "%d, %s, %d, %d, %s", entry.index, entry.name, entry.value, entry.description)
}

{
 const auto& entries = oatpp::Enum<MyEnum>::getEntries();
 for(const auto& e : entries) {
  ...
 }
}

DTO Hashcode and Equals

Now DTOs can be used as a Key in unordered_map and unordered_set. The convenience DTO_HC_EQ (DTO_HASHCODE_AND_EQUALS) macro has been added.

class User : public oatpp::DTO {

  DTO_INIT(User, DTO)

  DTO_FIELD(String, firstName);
  DTO_FIELD(String, lastName);

  DTO_HC_EQ(firstName, lastName) // List key fields that count in std::hash and "==","!=" operators. 

};

The DTO_HC_EQ macro works taking into account the DTO_HC_EQ declared in the parent DTO class.

If no DTO_HC_EQ is declared in none of the DTO's parent classes the default behavior is:

  • std::hash - is v_uint64 representation of object address.
  • operators == and != - is comparison of object addresses.

DTO Fields Annotation

Now it's possible to add a description for DTO fields, which will be automatically displayed in swagger-UI.

class MyDto : public oatpp::DTO {

  DTO_INIT(MyDto, DTO)

  DTO_FIELD_INFO(id) {
    info->description = "identifier";
  }
  DTO_FIELD(String, id);

};

Note: The description is currently the only info you can add to the DTO field (This may be extended later). In order to provide the list of possible values - use the new Enum feature - Type oatpp::Enum.