datatypes.iΒΆ

%module datatypes
%feature("autodoc", "0");

// Directly define the missing conversion macros for the C++ compiler.
// This ensures that any function returning a plain uint32_t or uint64_t
// can be correctly converted to a Python integer object.
%header %{
#define SWIG_From_uint32_t(val) PyLong_FromUnsignedLong(val)
#define SWIG_From_uint64_t(val) PyLong_FromUnsignedLongLong(val)
#define SWIG_From_double(val) PyFloat_FromDouble(val)
#define SWIG_From_int(val) PyLong_FromLong(val)
%}

%include <std_string.i>
%include <std_vector.i>
%include <typemaps.i>

%{
#include <optional>
#include <variant>
#include <string>
#include <vector>
#include <type_traits>
#include "datatypes.h"

// Forward-declare the SWIG helper function for std::string conversion
// to resolve potential compilation order issues.
SWIGINTERN PyObject* SWIG_From_std_string(const std::string& s);

// A visitor struct to convert a variant type to a PyObject*.
// This is a C++11/14 compatible alternative to a generic lambda and provides
// a clean way to organize the conversion logic.
struct variant_to_pyobject_visitor {
    using result_type = PyObject*;

    result_type operator()(int arg) const {
        return SWIG_From_int(arg);
    }
    result_type operator()(uint32_t arg) const {
        return SWIG_From_uint32_t(arg);
    }
    result_type operator()(uint64_t arg) const {
        return SWIG_From_uint64_t(arg);
    }
    result_type operator()(double arg) const {
        return SWIG_From_double(arg);
    }
    result_type operator()(const std::string& arg) const {
        // FIX: Check for and remove wrapping quotes from malformed SNMP strings.
        // https://github.com/carlkidcrypto/ezsnmp/issues/355
        if (arg.length() >= 2 && arg.front() == '"' && arg.back() == '"') {
            // Create a new C++ string containing just the inner content.
            std::string cleaned_str = arg.substr(1, arg.length() - 2);
            // Convert the *cleaned* string to a Python object.
            return SWIG_From_std_string(cleaned_str);
        }
        
        // If no wrapping quotes are found, convert the original string as usual.
        return SWIG_From_std_string(arg);
    }
    result_type operator()(const std::vector<unsigned char>& arg) const {
        return PyBytes_FromStringAndSize(reinterpret_cast<const char*>(arg.data()), arg.size());
    }
};
%}

// ---- START: ROBUST VARIANT SUPPORT ----
// Provide a simplified declaration for std::variant for SWIG's parser.
template<typename... Ts> class std::variant {};

// This is the core logic for converting a C++ variant to a Python object.
// It uses a series of `if-else if` checks with `std::get_if` as a robust
// alternative to `std::visit`, which is unavailable on older macOS targets.
%define VARIANT_OUT_LOGIC(INPUT)
  // Create an instance of our visitor to handle the type-specific conversions.
  variant_to_pyobject_visitor visitor;

  // Check the type held by the variant and call the appropriate visitor function.
  // std::get_if returns a pointer to the value if the variant holds that type,
  // otherwise it returns nullptr.
  if (auto* val = std::get_if<int>(&(INPUT))) {
    $result = visitor(*val);
  } else if (auto* val = std::get_if<uint32_t>(&(INPUT))) {
    $result = visitor(*val);
  } else if (auto* val = std::get_if<uint64_t>(&(INPUT))) {
    $result = visitor(*val);
  } else if (auto* val = std::get_if<double>(&(INPUT))) {
    $result = visitor(*val);
  } else if (auto* val = std::get_if<std::string>(&(INPUT))) {
    $result = visitor(*val);
  } else if (auto* val = std::get_if<std::vector<unsigned char>>(&(INPUT))) {
    $result = visitor(*val);
  } else {
    // This case should not be reached if the variant is valid.
    // Set a Python exception to indicate a problem.
    SWIG_exception_fail(SWIG_TypeError, "Variant holds an unexpected or unhandled type.");
  }
%enddef

// Define the full variant type for convenience and clarity.
// Typemap for returning a variant by VALUE or by CONST REFERENCE.
%typemap(out) std::variant<int, uint32_t, uint64_t, double, std::string, std::vector<unsigned char>>,
              const std::variant<int, uint32_t, uint64_t, double, std::string, std::vector<unsigned char>>& {
  VARIANT_OUT_LOGIC($1)
}

// Typemap for returning a variant by POINTER.
%typemap(out) std::variant<int, uint32_t, uint64_t, double, std::string, std::vector<unsigned char>>* {
  if (!$1) {
    $result = Py_None;
    Py_INCREF(Py_None);
  } else {
    // Note the dereference of the pointer: *$1
    VARIANT_OUT_LOGIC(*$1)
  }
}

// Tell SWIG to generate the wrapper for our specific variant instantiation.
%template(ConvertedValue) std::variant<int, uint32_t, uint64_t, double, std::string, std::vector<unsigned char>>;

// ---- END: ROBUST VARIANT SUPPORT ----

// Include the header file
%include "../include/datatypes.h"