summaryrefslogtreecommitdiffstats
path: root/toolkit/components/uniffi-js/ScaffoldingConverter.h
blob: 59829938c2e25e8ee3f904509a60d94aea476d91 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sw=2 sts=2 et cindent: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#ifndef mozilla_ScaffoldingConverter_h
#define mozilla_ScaffoldingConverter_h

#include <limits>
#include <type_traits>
#include "nsString.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/dom/OwnedRustBuffer.h"
#include "mozilla/dom/PrimitiveConversions.h"
#include "mozilla/dom/TypedArray.h"
#include "mozilla/dom/UniFFIBinding.h"
#include "mozilla/dom/UniFFIPointer.h"
#include "mozilla/dom/UniFFIPointerType.h"
#include "mozilla/dom/UniFFIRust.h"
#include "mozilla/dom/UniFFIScaffolding.h"

namespace mozilla::uniffi {

class ScaffoldingConverterTagDefault {};

// Handle converting types between JS and Rust
//
// Scaffolding conversions are done using a 2 step process:
//   - Call FromJs/FromRust to convert to an intermediate type
//   - Call IntoJs/IntoRust to convert from that type to the target type
//
// The main reason for this is handling RustBuffers when other arguments fail
// to convert.  By using OwnedRustBuffer as the intermediate type, we can
// ensure those buffers get freed in that case.  Note that we can't use
// OwnedRustBuffer as the Rust type.  Passing the buffer into Rust transfers
// ownership so we shouldn't free the buffer in this case.
//
// For most other types, we just use the Rust type as the intermediate type.
template <typename T, typename Tag = ScaffoldingConverterTagDefault>
class ScaffoldingConverter {
 public:
  using RustType = T;
  using IntermediateType = T;

  // Convert a JS value to an intermedate type
  //
  // This inputs a const ref, because that's what the WebIDL bindings send to
  // us.
  //
  // If this succeeds then IntoRust is also guaranteed to succeed
  static mozilla::Result<IntermediateType, nsCString> FromJs(
      const dom::ScaffoldingType& aValue) {
    if (!aValue.IsDouble()) {
      return Err("Bad argument type"_ns);
    }
    double value = aValue.GetAsDouble();

    if (std::isnan(value)) {
      return Err("NaN not allowed"_ns);
    }

    if constexpr (std::is_integral<RustType>::value) {
      // Use PrimitiveConversionTraits_Limits rather than std::numeric_limits,
      // since it handles JS-specific bounds like the 64-bit integer limits.
      // (see Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER)
      if (value < dom::PrimitiveConversionTraits_Limits<RustType>::min() ||
          value > dom::PrimitiveConversionTraits_Limits<RustType>::max()) {
        return Err("Out of bounds"_ns);
      }
    }

    // Don't check float bounds for a few reasons.
    //   - It's difficult because
    //     PrimitiveConversionTraits_Limits<float>::min() is the smallest
    //     positive value, rather than the most negative.
    //   - A float value unlikely to overflow
    //   - It's also likely that we can't do an exact conversion because the
    //     float doesn't have enough precision, but it doesn't seem correct
    //     to error out in that case.

    RustType rv = static_cast<RustType>(value);
    if constexpr (std::is_integral<RustType>::value) {
      if (rv != value) {
        return Err("Not an integer"_ns);
      }
    }

    return rv;
  }

  // Convert an intermediate type to a Rust type
  //
  // IntoRust doesn't touch the JS data, so it's safe to call in a worker thread
  static RustType IntoRust(IntermediateType aValue) { return aValue; }

  // Convert an Rust type to an intermediate type
  //
  // This inputs a value since RustTypes are POD types
  //
  // If this succeeds then IntoJs is also guaranteed to succeed
  static mozilla::Result<IntermediateType, nsCString> FromRust(
      RustType aValue) {
    if constexpr (std::is_same<RustType, int64_t>::value ||
                  std::is_same<RustType, uint64_t>::value) {
      // Check that the value can fit in a double (only needed for 64 bit types)
      if (aValue < dom::PrimitiveConversionTraits_Limits<RustType>::min() ||
          aValue > dom::PrimitiveConversionTraits_Limits<RustType>::max()) {
        return Err("Out of bounds"_ns);
      }
    }
    if constexpr (std::is_floating_point<RustType>::value) {
      if (std::isnan(aValue)) {
        return Err("NaN not allowed"_ns);
      }
    }
    return aValue;
  }

  // Convert an intermedate type to a JS type
  //
  // This inputs an r-value reference since we may want to move data out of
  // this type.
  static void IntoJs(JSContext* aContext, IntermediateType&& aValue,
                     dom::ScaffoldingType& aDest) {
    aDest.SetAsDouble() = aValue;
  }
};

template <>
class ScaffoldingConverter<RustBuffer> {
 public:
  using RustType = RustBuffer;
  using IntermediateType = OwnedRustBuffer;

  static mozilla::Result<OwnedRustBuffer, nsCString> FromJs(
      const dom::ScaffoldingType& aValue) {
    if (!aValue.IsArrayBuffer()) {
      return Err("Bad argument type"_ns);
    }

    const dom::ArrayBuffer& arrayBuf = aValue.GetAsArrayBuffer();
    arrayBuf.ComputeState();
    return OwnedRustBuffer::FromArrayBuffer(arrayBuf);
  }

  static RustBuffer IntoRust(OwnedRustBuffer&& aValue) {
    return aValue.IntoRustBuffer();
  }

  static mozilla::Result<OwnedRustBuffer, nsCString> FromRust(
      RustBuffer aValue) {
    return OwnedRustBuffer(aValue);
  }

  static void IntoJs(JSContext* aContext, OwnedRustBuffer&& aValue,
                     dom::ScaffoldingType& aDest) {
    aDest.SetAsArrayBuffer().Init(aValue.IntoArrayBuffer(aContext));
  }
};

// ScaffoldingConverter for object pointers
template <const UniFFIPointerType* PointerType>
class ScaffoldingObjectConverter {
 public:
  using RustType = void*;
  using IntermediateType = void*;

  static mozilla::Result<void*, nsCString> FromJs(
      const dom::ScaffoldingType& aValue) {
    if (!aValue.IsUniFFIPointer()) {
      return Err("Bad argument type"_ns);
    }
    dom::UniFFIPointer& value = aValue.GetAsUniFFIPointer();
    if (!value.IsSamePtrType(PointerType)) {
      return Err("Bad pointer type"_ns);
    }
    return value.GetPtr();
  }

  static void* IntoRust(void* aValue) { return aValue; }

  static mozilla::Result<void*, nsCString> FromRust(void* aValue) {
    return aValue;
  }

  static void IntoJs(JSContext* aContext, void* aValue,
                     dom::ScaffoldingType& aDest) {
    aDest.SetAsUniFFIPointer() =
        dom::UniFFIPointer::Create(aValue, PointerType);
  }
};

// ScaffoldingConverter for void returns
//
// This doesn't implement the normal interface, it's only use is a the
// ReturnConverter parameter of ScaffoldingCallHandler.
template <>
class ScaffoldingConverter<void> {
 public:
  using RustType = void;
};

}  // namespace mozilla::uniffi

#endif  // mozilla_ScaffoldingConverter_h