Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Expose field names via a public API #1284

Open
EricRabil opened this issue Aug 26, 2022 · 1 comment
Open

Expose field names via a public API #1284

EricRabil opened this issue Aug 26, 2022 · 1 comment

Comments

@EricRabil
Copy link

Being able to access this would've allowed me to easily log the name of the payload being processed, as I am using oneof to enumerate all possible IPC payloads. I think this is a more than acceptable use case.

IMO, this data has been sitting in the library for 5 years and counting. It clearly works well enough, even if it is "not quite correct" or a "special-case". If the alternative is reflection, which is not performant, why should this remain an internal API? More importantly, why is the library the only one with easy access to this information?

My workaround uses reflection, building atop of the workaround here:

var nameMapCache: [String: [Int: String]] = [:]
extension _NameMap {
    func protoNameFor(rawValue: Int, cacheKey: String) -> String? {
        if let cache = nameMapCache[cacheKey] {
            return cache[rawValue]
        }
        let selfMirror = Mirror(reflecting: self)
        
        guard let numberToNameMapChild = selfMirror.children.first(where: { (name, _) -> Bool in
            return name == "numberToNameMap"
        }), let numberToNameMap = numberToNameMapChild.value as? Dictionary<Int, Any> else {
                return nil
        }

        nameMapCache[cacheKey] = numberToNameMap.compactMapValues { value in
            let valueMirror = Mirror(reflecting: value)
            
            guard let protoChild = valueMirror.children.first(where: { (name, _) -> Bool in
                return name == "proto"
            }), let stringConvertible = protoChild.value as? CustomStringConvertible else {
                return nil
            }
            
            return stringConvertible.description
        }
        return protoNameFor(rawValue: rawValue, cacheKey: cacheKey)
    }
}

struct PBPayloadCommandNameReader: SwiftProtobuf.Visitor {
    let start: Int

    mutating func visitSingularDoubleField(value: Double, fieldNumber: Int) throws {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitSingularInt64Field(value: Int64, fieldNumber: Int) throws {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitSingularUInt64Field(value: UInt64, fieldNumber: Int) throws {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitSingularBoolField(value: Bool, fieldNumber: Int) throws {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitSingularStringField(value: String, fieldNumber: Int) throws {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitSingularBytesField(value: Data, fieldNumber: Int) throws {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitSingularEnumField<E>(value: E, fieldNumber: Int) throws where E : Enum {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitMapField<KeyType, ValueType>(fieldType: _ProtobufMap<KeyType, ValueType>.Type, value: _ProtobufMap<KeyType, ValueType>.BaseType, fieldNumber: Int) throws where KeyType : MapKeyType, ValueType : MapValueType {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitMapField<KeyType, ValueType>(fieldType: _ProtobufEnumMap<KeyType, ValueType>.Type, value: _ProtobufEnumMap<KeyType, ValueType>.BaseType, fieldNumber: Int) throws where KeyType : MapKeyType, ValueType : Enum, ValueType.RawValue == Int {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitMapField<KeyType, ValueType>(fieldType: _ProtobufMessageMap<KeyType, ValueType>.Type, value: _ProtobufMessageMap<KeyType, ValueType>.BaseType, fieldNumber: Int) throws where KeyType : MapKeyType, ValueType : Hashable, ValueType : SwiftProtobuf.Message {
        try check(fieldNumber: fieldNumber)
    }

    mutating func visitUnknown(bytes: Data) throws {
    }

    enum Found: Error { case found(String?) }
    mutating func visitSingularMessageField<M>(value: M, fieldNumber: Int) throws where M : SwiftProtobuf.Message {
        try check(fieldNumber: fieldNumber)
    }

    func check(fieldNumber: Int) throws {
        guard fieldNumber >= start else {
            return
        }
        throw Found.found(PBPayload._protobuf_nameMap.protoNameFor(rawValue: fieldNumber, cacheKey: "PBPayloadCommandReflection"))
    }
}

/// PBPayload { int64 ID = 1; bool IsResponse = 2; oneof Command { ... }; }
extension PBPayload {
    var commandName: String? {
        var reader = PBPayloadCommandNameReader(start: 3)
        do {
            try traverse(visitor: &reader)
        } catch {
            if case PBPayloadCommandNameReader.Found.found(let name) = error {
                return name
            }
            fatalError("Error while traversing: \(error)")
        }
        return nil
    }
}

Having this functionality provided by the library would be incredibly valuable, and would cut out all of this boilerplate code.

@cassianomonteiro
Copy link

cassianomonteiro commented Apr 26, 2023

+1, it would be really useful with enums using string constants.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants