Skip to content

Commit

Permalink
feat: Add enums to the HybridObject (#7)
Browse files Browse the repository at this point in the history
Adds support for enums to `HybridObject`:


When declaring custom enums there's two steps you need to do:

1. Create your enum (e.g. in `TestEnum.h`)

```cpp
enum TestEnum {
    FIRST,
    SECOND,
    THIRD
};
```

2. Add the enum to the `EnumMapper` by editing `cpp/jsi/EnumMapper.h`:

```cpp
template<>
struct EnumMapper<TestEnum> {
public:
    static constexpr TestEnum fromJSUnion(const std::string& jsUnion) {
        if (jsUnion == "first") return FIRST;
        if (jsUnion == "second") return SECOND;
        if (jsUnion == "third") return THIRD;
        throw invalidUnion(jsUnion);
    }
    static std::string toJSUnion(TestEnum value) {
        switch (value) {
            case FIRST: return "first";
            case SECOND: return "second";
            case THIRD: return "third";
        }
        throw invalidEnum(value);
    }
};
```

This way compile-time safety is guaranteed.

All other approaches to parse enums from C++ to JS are not compile-time
but rather run-time, so currently there's no way around editing
`EnumMapper.h` unless we do runtime type checking.
  • Loading branch information
mrousavy authored Feb 22, 2024
1 parent 42a1c60 commit ba2313a
Show file tree
Hide file tree
Showing 7 changed files with 106 additions and 4 deletions.
60 changes: 60 additions & 0 deletions package/cpp/jsi/EnumMapper.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
//
// Created by Marc Rousavy on 22.02.24.
//

#pragma once

#include "test/TestEnum.h"
#include <unordered_map>

namespace margelo {

using namespace facebook;

static std::runtime_error invalidUnion(const std::string jsUnion) {
return std::runtime_error("Cannot convert JS Value to Enum: Invalid Union value passed! (\"" + jsUnion + "\")");
}
template <typename Enum> static std::runtime_error invalidEnum(Enum passedEnum) {
return std::runtime_error("Cannot convert Enum to JS Value: Invalid Enum passed! (Value #" + std::to_string(passedEnum) +
" does not exist in " + typeid(Enum).name() + ")");
}

template <typename Enum> struct EnumMapper {
static Enum fromJSUnion(const std::string&) {
static_assert(always_false<Enum>::value, "This type is not supported by the EnumMapper!");
return Enum();
}
static std::string toJSUnion(Enum) {
static_assert(always_false<Enum>::value, "This type is not supported by the EnumMapper!");
return std::string();
}

private:
template <typename> struct always_false : std::false_type {};
};

template <> struct EnumMapper<TestEnum> {
public:
static constexpr TestEnum fromJSUnion(const std::string& jsUnion) {
if (jsUnion == "first")
return FIRST;
if (jsUnion == "second")
return SECOND;
if (jsUnion == "third")
return THIRD;
throw invalidUnion(jsUnion);
}
static std::string toJSUnion(TestEnum value) {
switch (value) {
case FIRST:
return "first";
case SECOND:
return "second";
case THIRD:
return "third";
}
throw invalidEnum(value);
}
};

} // namespace margelo
12 changes: 12 additions & 0 deletions package/cpp/jsi/JSIConverter.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include "EnumMapper.h"
#include "HybridObject.h"
#include <array>
#include <jsi/jsi.h>
Expand Down Expand Up @@ -100,6 +101,17 @@ template <typename TInner> struct JSIConverter<std::optional<TInner>> {
}
};

template <typename TEnum> struct JSIConverter<TEnum, std::enable_if_t<std::is_enum<TEnum>::value>> {
static TEnum fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
std::string string = arg.asString(runtime).utf8(runtime);
return EnumMapper<TEnum>::fromJSUnion(string);
}
static jsi::Value toJSI(jsi::Runtime& runtime, TEnum arg) {
std::string string = EnumMapper<TEnum>::toJSUnion(arg);
return jsi::String::createFromUtf8(runtime, string);
}
};

template <typename ReturnType, typename... Args> struct JSIConverter<std::function<ReturnType(Args...)>> {
static std::function<ReturnType(Args...)> fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
jsi::Function function = arg.asObject(runtime).asFunction(runtime);
Expand Down
13 changes: 13 additions & 0 deletions package/cpp/test/TestEnum.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Created by Marc Rousavy on 22.02.24.
//

#pragma once

#include "jsi/EnumMapper.h"

namespace margelo {

enum TestEnum { FIRST, SECOND, THIRD };

}
3 changes: 3 additions & 0 deletions package/cpp/test/TestHybridObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ void TestHybridObject::loadHybridMethods() {
// this.string get & set
registerHybridGetter("string", &TestHybridObject::getString, this);
registerHybridSetter("string", &TestHybridObject::setString, this);
// this.enum
registerHybridGetter("enum", &TestHybridObject::getEnum, this);
registerHybridSetter("enum", &TestHybridObject::setEnum, this);
// methods
registerHybridMethod("multipleArguments", &TestHybridObject::multipleArguments, this);
// callbacks
Expand Down
8 changes: 8 additions & 0 deletions package/cpp/test/TestHybridObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

#pragma once

#include "TestEnum.h"
#include "jsi/HybridObject.h"
#include <string>
#include <vector>
Expand All @@ -24,6 +25,12 @@ class TestHybridObject : public HybridObject {
void setString(std::string newValue) {
_string = newValue;
}
void setEnum(TestEnum testEnum) {
_enum = testEnum;
}
TestEnum getEnum() {
return _enum;
}

std::unordered_map<std::string, double> multipleArguments(int first, bool second, std::string third) {
return std::unordered_map<std::string, double>{{"first", 5312}, {"second", 532233}, {"third", 2786}};
Expand All @@ -42,6 +49,7 @@ class TestHybridObject : public HybridObject {
private:
int _int;
std::string _string;
TestEnum _enum;

void loadHybridMethods() override;
};
Expand Down
1 change: 1 addition & 0 deletions package/src/native/FilamentProxy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ interface TestHybridObject {
getIntGetter(): () => number
sayHelloCallback(callback: () => string): void
createNewHybridObject: () => TestHybridObject
enum: 'first' | 'second' | 'third'
}

export interface TFilamentProxy {
Expand Down
13 changes: 9 additions & 4 deletions package/src/test/TestHybridObject.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,26 @@ export function testHybridObject() {
hybridObject.int = 6723
console.log(`New Int: ${hybridObject.int}`)

// 4. String Getter & Setter
// 4. Enum Getter & Setter
console.log(`Enum: ${hybridObject.enum}`)
hybridObject.enum = 'second'
console.log(`New Enum: ${hybridObject.enum}`)

// 5. String Getter & Setter
console.log(`String: ${hybridObject.string}`)
hybridObject.string = 'new string value!'
console.log(`New String: ${hybridObject.string}`)

// 5. Testing multiple arguments and maps
// 6. Testing multiple arguments and maps
const result = hybridObject.multipleArguments(5, true, 'hahah!')
console.log(`multipleArguments() -> ${JSON.stringify(result)}`)

// 6. Testing callbacks
// 7. Testing callbacks
hybridObject.sayHelloCallback(() => 'hello from JS!')
const getter = hybridObject.getIntGetter()
console.log(`Int getter: ${getter()}`)

// 7. Create a new one
// 8. Create a new one
const newObject = hybridObject.createNewHybridObject()
console.log(`Created new hybrid object!`, newObject)

Expand Down

0 comments on commit ba2313a

Please sign in to comment.