@homaiohq/napi-quickfix 0.1.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CMakeLists.txt +5 -1
- package/README.md +344 -18
- package/cpp/acceptor_wrap.cpp +253 -19
- package/cpp/acceptor_wrap.h +60 -1
- package/cpp/addon.cpp +10 -6
- package/cpp/application_bridge.cpp +114 -30
- package/cpp/application_bridge.h +57 -4
- package/cpp/data_dictionary_wrap.cpp +89 -3
- package/cpp/data_dictionary_wrap.h +27 -5
- package/cpp/engine_common.h +108 -0
- package/cpp/engine_workers.h +99 -18
- package/cpp/field_map_ops.h +492 -0
- package/cpp/group_wrap.cpp +221 -0
- package/cpp/group_wrap.h +76 -0
- package/cpp/initiator_wrap.cpp +262 -25
- package/cpp/initiator_wrap.h +37 -2
- package/cpp/message_wrap.cpp +171 -78
- package/cpp/message_wrap.h +47 -5
- package/cpp/session_op_gate.h +201 -0
- package/cpp/session_static.cpp +139 -12
- package/cpp/session_wrap.cpp +438 -0
- package/cpp/session_wrap.h +144 -0
- package/dist/cjs/acceptor.d.ts +0 -1
- package/dist/cjs/acceptor.js +0 -1
- package/dist/cjs/application.d.ts +0 -1
- package/dist/cjs/application.js +0 -1
- package/dist/cjs/data-dictionary.d.ts +21 -3
- package/dist/cjs/data-dictionary.js +32 -4
- package/dist/cjs/engine.d.ts +42 -4
- package/dist/cjs/engine.js +50 -5
- package/dist/cjs/enums.d.ts +48 -14
- package/dist/cjs/enums.js +20 -25
- package/dist/cjs/field-value.d.ts +12 -0
- package/dist/cjs/field-value.js +40 -0
- package/dist/cjs/generated/fields.d.ts +0 -1
- package/dist/cjs/generated/fields.js +0 -1
- package/dist/cjs/generated/values.d.ts +8548 -0
- package/dist/cjs/generated/values.js +8580 -0
- package/dist/cjs/group.d.ts +126 -0
- package/dist/cjs/group.js +185 -0
- package/dist/cjs/index.d.ts +28 -5
- package/dist/cjs/index.js +30 -28
- package/dist/cjs/initiator.d.ts +0 -1
- package/dist/cjs/initiator.js +0 -1
- package/dist/cjs/message.d.ts +129 -5
- package/dist/cjs/message.js +146 -9
- package/dist/cjs/native.d.ts +179 -17
- package/dist/cjs/native.js +0 -1
- package/dist/cjs/session-id.d.ts +12 -1
- package/dist/cjs/session-id.js +21 -1
- package/dist/cjs/session-settings.d.ts +0 -1
- package/dist/cjs/session-settings.js +0 -1
- package/dist/cjs/session.d.ts +213 -0
- package/dist/cjs/session.js +337 -0
- package/dist/cjs/tls-tunnel.d.ts +143 -0
- package/dist/cjs/tls-tunnel.js +261 -0
- package/dist/esm/acceptor.d.ts +0 -1
- package/dist/esm/acceptor.js +0 -1
- package/dist/esm/application.d.ts +0 -1
- package/dist/esm/application.js +0 -1
- package/dist/esm/data-dictionary.d.ts +21 -3
- package/dist/esm/data-dictionary.js +32 -4
- package/dist/esm/engine.d.ts +42 -4
- package/dist/esm/engine.js +51 -6
- package/dist/esm/enums.d.ts +48 -14
- package/dist/esm/enums.js +14 -24
- package/dist/esm/field-value.d.ts +12 -0
- package/dist/esm/field-value.js +37 -0
- package/dist/esm/generated/fields.d.ts +0 -1
- package/dist/esm/generated/fields.js +0 -1
- package/dist/esm/generated/values.d.ts +8548 -0
- package/dist/esm/generated/values.js +8564 -0
- package/dist/esm/group.d.ts +126 -0
- package/dist/esm/group.js +181 -0
- package/dist/esm/index.d.ts +28 -5
- package/dist/esm/index.js +18 -26
- package/dist/esm/initiator.d.ts +0 -1
- package/dist/esm/initiator.js +0 -1
- package/dist/esm/message.d.ts +129 -5
- package/dist/esm/message.js +146 -9
- package/dist/esm/native.d.ts +179 -17
- package/dist/esm/native.js +0 -1
- package/dist/esm/session-id.d.ts +12 -1
- package/dist/esm/session-id.js +20 -1
- package/dist/esm/session-settings.d.ts +0 -1
- package/dist/esm/session-settings.js +0 -1
- package/dist/esm/session.d.ts +213 -0
- package/dist/esm/session.js +329 -0
- package/dist/esm/tls-tunnel.d.ts +143 -0
- package/dist/esm/tls-tunnel.js +256 -0
- package/package.json +13 -1
- package/prebuilds/darwin-arm64/node.napi.node +0 -0
- package/prebuilds/darwin-x64/node.napi.node +0 -0
- package/prebuilds/linux-x64/node.napi.glibc.node +0 -0
- package/prebuilds/linux-x64/node.napi.musl.node +0 -0
- package/prebuilds/win32-x64/node.napi.node +0 -0
- package/cpp/enums.cpp +0 -66
- package/dist/cjs/acceptor.d.ts.map +0 -1
- package/dist/cjs/acceptor.js.map +0 -1
- package/dist/cjs/application.d.ts.map +0 -1
- package/dist/cjs/application.js.map +0 -1
- package/dist/cjs/data-dictionary.d.ts.map +0 -1
- package/dist/cjs/data-dictionary.js.map +0 -1
- package/dist/cjs/engine.d.ts.map +0 -1
- package/dist/cjs/engine.js.map +0 -1
- package/dist/cjs/enums.d.ts.map +0 -1
- package/dist/cjs/enums.js.map +0 -1
- package/dist/cjs/generated/fields.d.ts.map +0 -1
- package/dist/cjs/generated/fields.js.map +0 -1
- package/dist/cjs/index.d.ts.map +0 -1
- package/dist/cjs/index.js.map +0 -1
- package/dist/cjs/initiator.d.ts.map +0 -1
- package/dist/cjs/initiator.js.map +0 -1
- package/dist/cjs/message.d.ts.map +0 -1
- package/dist/cjs/message.js.map +0 -1
- package/dist/cjs/native.d.ts.map +0 -1
- package/dist/cjs/native.js.map +0 -1
- package/dist/cjs/session-id.d.ts.map +0 -1
- package/dist/cjs/session-id.js.map +0 -1
- package/dist/cjs/session-settings.d.ts.map +0 -1
- package/dist/cjs/session-settings.js.map +0 -1
- package/dist/esm/acceptor.d.ts.map +0 -1
- package/dist/esm/acceptor.js.map +0 -1
- package/dist/esm/application.d.ts.map +0 -1
- package/dist/esm/application.js.map +0 -1
- package/dist/esm/data-dictionary.d.ts.map +0 -1
- package/dist/esm/data-dictionary.js.map +0 -1
- package/dist/esm/engine.d.ts.map +0 -1
- package/dist/esm/engine.js.map +0 -1
- package/dist/esm/enums.d.ts.map +0 -1
- package/dist/esm/enums.js.map +0 -1
- package/dist/esm/generated/fields.d.ts.map +0 -1
- package/dist/esm/generated/fields.js.map +0 -1
- package/dist/esm/index.d.ts.map +0 -1
- package/dist/esm/index.js.map +0 -1
- package/dist/esm/initiator.d.ts.map +0 -1
- package/dist/esm/initiator.js.map +0 -1
- package/dist/esm/message.d.ts.map +0 -1
- package/dist/esm/message.js.map +0 -1
- package/dist/esm/native.d.ts.map +0 -1
- package/dist/esm/native.js.map +0 -1
- package/dist/esm/session-id.d.ts.map +0 -1
- package/dist/esm/session-id.js.map +0 -1
- package/dist/esm/session-settings.d.ts.map +0 -1
- package/dist/esm/session-settings.js.map +0 -1
|
@@ -0,0 +1,492 @@
|
|
|
1
|
+
// FieldMap operations shared by MessageWrap and GroupWrap.
|
|
2
|
+
//
|
|
3
|
+
// FIX::Message (its body), FIX::Header, FIX::Trailer and FIX::Group are all
|
|
4
|
+
// FIX::FieldMap. The JS-facing field and repeating-group methods are therefore
|
|
5
|
+
// written once here as templates over a `Select` callable:
|
|
6
|
+
//
|
|
7
|
+
// FIX::FieldMap& select(int tag)
|
|
8
|
+
//
|
|
9
|
+
// which picks the FieldMap that owns `tag`. MessageWrap routes well-known
|
|
10
|
+
// header/trailer tags to the right section; GroupWrap always returns itself.
|
|
11
|
+
// Each op parses its own arguments from the CallbackInfo, so the wrappers are
|
|
12
|
+
// one-line delegations.
|
|
13
|
+
//
|
|
14
|
+
// Group indices are 1-based, as in QuickFIX.
|
|
15
|
+
#ifndef NAPI_QUICKFIX_FIELD_MAP_OPS_H
|
|
16
|
+
#define NAPI_QUICKFIX_FIELD_MAP_OPS_H
|
|
17
|
+
|
|
18
|
+
#include <napi.h>
|
|
19
|
+
|
|
20
|
+
#include <cstdint>
|
|
21
|
+
#include <limits>
|
|
22
|
+
#include <memory>
|
|
23
|
+
#include <string>
|
|
24
|
+
#include <utility>
|
|
25
|
+
#include <vector>
|
|
26
|
+
|
|
27
|
+
#include "errors.h"
|
|
28
|
+
#include "group_wrap.h"
|
|
29
|
+
#include "quickfix/FieldMap.h"
|
|
30
|
+
#include "quickfix/Group.h"
|
|
31
|
+
#include "quickfix/Message.h"
|
|
32
|
+
|
|
33
|
+
namespace napi_quickfix {
|
|
34
|
+
namespace fieldmap {
|
|
35
|
+
|
|
36
|
+
// --- argument coercion ------------------------------------------------------
|
|
37
|
+
|
|
38
|
+
inline bool IsNullish(Napi::Value v) { return v.IsUndefined() || v.IsNull(); }
|
|
39
|
+
|
|
40
|
+
// A JS number that is an integer within int range (no NaN, no fraction), as
|
|
41
|
+
// an int. Int32Value() would silently truncate 1.5 to 1 and NaN to 0.
|
|
42
|
+
inline bool ToInteger(Napi::Value v, int& out) {
|
|
43
|
+
if (!v.IsNumber()) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const double d = v.As<Napi::Number>().DoubleValue();
|
|
47
|
+
if (!(d >= std::numeric_limits<int>::min() &&
|
|
48
|
+
d <= std::numeric_limits<int>::max())) {
|
|
49
|
+
return false; // NaN, infinities and out-of-range values all land here
|
|
50
|
+
}
|
|
51
|
+
const int i = static_cast<int>(d);
|
|
52
|
+
if (static_cast<double>(i) != d) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
out = i;
|
|
56
|
+
return true;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// A field tag for reads (getField, isSetField, ...): any integer. A tag that
|
|
60
|
+
// no message can hold (0, negative) simply reads as absent / FieldNotFound.
|
|
61
|
+
inline int CoerceTag(Napi::Env env, Napi::Value v) {
|
|
62
|
+
int tag;
|
|
63
|
+
if (!ToInteger(v, tag)) {
|
|
64
|
+
throw Napi::TypeError::New(env, "field tag must be an integer");
|
|
65
|
+
}
|
|
66
|
+
return tag;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// A field tag for writes (setField): a positive integer, since QuickFIX would
|
|
70
|
+
// happily emit "0=x" or "-1=x" on the wire.
|
|
71
|
+
inline int CoerceSetTag(Napi::Env env, Napi::Value v) {
|
|
72
|
+
int tag;
|
|
73
|
+
if (!ToInteger(v, tag) || tag <= 0) {
|
|
74
|
+
throw Napi::TypeError::New(env, "field tag must be a positive integer");
|
|
75
|
+
}
|
|
76
|
+
return tag;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// The largest tag accepted in a group's field order (delimiter included).
|
|
80
|
+
// QuickFIX's message_order allocates and zero-fills `largestTag + 1` ints per
|
|
81
|
+
// group, so an unbounded tag would mean a multi-GB allocation (or a signed
|
|
82
|
+
// overflow at INT_MAX) inside the Group constructor, where a std::bad_alloc
|
|
83
|
+
// is not translated and aborts the process. 2^20 keeps that at 4 MiB while
|
|
84
|
+
// leaving every standard and user-defined tag range (< 100000) usable.
|
|
85
|
+
constexpr int kMaxOrderTag = 1 << 20;
|
|
86
|
+
|
|
87
|
+
// A delimiter or group-order tag: a positive integer bounded by kMaxOrderTag.
|
|
88
|
+
inline int CheckOrderTag(Napi::Env env, int tag) {
|
|
89
|
+
if (tag > kMaxOrderTag) {
|
|
90
|
+
throw Napi::RangeError::New(
|
|
91
|
+
env, "group order field tags must be at most " +
|
|
92
|
+
std::to_string(kMaxOrderTag));
|
|
93
|
+
}
|
|
94
|
+
return tag;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Group index (1-based): an integer. Range errors are handled per operation
|
|
98
|
+
// (FieldNotFound, false, or a no-op) since QuickFIX does not bound index 0.
|
|
99
|
+
inline int CoerceIndex(Napi::Env env, Napi::Value v) {
|
|
100
|
+
int index;
|
|
101
|
+
if (!ToInteger(v, index)) {
|
|
102
|
+
throw Napi::TypeError::New(env, "group index must be an integer");
|
|
103
|
+
}
|
|
104
|
+
return index;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Coerce a JS arg to a FIX field value string. Accepts string or number
|
|
108
|
+
// (numbers are stringified without trailing ".0"). Throws TypeError otherwise.
|
|
109
|
+
inline std::string CoerceValue(Napi::Env env, Napi::Value v) {
|
|
110
|
+
if (v.IsString()) {
|
|
111
|
+
return v.As<Napi::String>().Utf8Value();
|
|
112
|
+
}
|
|
113
|
+
if (v.IsNumber()) {
|
|
114
|
+
double d = v.As<Napi::Number>().DoubleValue();
|
|
115
|
+
if (d == static_cast<double>(static_cast<long long>(d))) {
|
|
116
|
+
return std::to_string(static_cast<long long>(d));
|
|
117
|
+
}
|
|
118
|
+
return std::to_string(d);
|
|
119
|
+
}
|
|
120
|
+
throw Napi::TypeError::New(env, "field value must be a string or number");
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// --- typed copies -----------------------------------------------------------
|
|
124
|
+
|
|
125
|
+
// The delimiter of a stored group entry. Entries are stored as FIX::Group
|
|
126
|
+
// (the ones QuickFIX parses with a dictionary, the ones AddGroup stores and
|
|
127
|
+
// the ones Clone rebuilds), so the dynamic type normally carries it. The
|
|
128
|
+
// fallback covers a plain-FieldMap entry QuickFIX itself stored (e.g. with
|
|
129
|
+
// FieldMap::addGroup, or by copying a message it queued out of order): its
|
|
130
|
+
// first field, which is the delimiter when the entry holds it (every group
|
|
131
|
+
// order sorts it first), and only a guess when it does not.
|
|
132
|
+
inline int DelimOf(const FIX::FieldMap& stored) {
|
|
133
|
+
if (const auto* g = dynamic_cast<const FIX::Group*>(&stored)) {
|
|
134
|
+
return g->delim();
|
|
135
|
+
}
|
|
136
|
+
if (stored.begin() != stored.end()) {
|
|
137
|
+
return stored.begin()->getTag();
|
|
138
|
+
}
|
|
139
|
+
return 0;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// The field order a FIX::Group is built with when its contents, field order
|
|
143
|
+
// included, are assigned over it right after (Clone, RebuildGroups' move).
|
|
144
|
+
// Group(field, delim) would build message_order(delim, 0), allocating and
|
|
145
|
+
// zero-filling delim + 1 ints only to discard them, and delim may be DelimOf's
|
|
146
|
+
// first-field guess, which kMaxOrderTag does not bound.
|
|
147
|
+
inline FIX::message_order PlaceholderOrder() {
|
|
148
|
+
return FIX::message_order(FIX::message_order::normal);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// FieldMap::operator= (hence every copy of a FIX::Message or FIX::Group,
|
|
152
|
+
// FIX::Group's copy constructor included) stores each nested entry as
|
|
153
|
+
// `new FieldMap(*entry)`: the fields and field order survive, but the entry's
|
|
154
|
+
// FIX::Group type, and with it its delimiter, is sliced away. Clone copies
|
|
155
|
+
// like operator= and then rebuilds every nested entry as a FIX::Group, so the
|
|
156
|
+
// delimiter of an entry that is empty or lacks its delimiter field is not lost
|
|
157
|
+
// on the way from the engine (or a JS Group) to a snapshot.
|
|
158
|
+
|
|
159
|
+
// Rebuild the nested entries of `dst`, a FieldMap::operator= copy of `src`, as
|
|
160
|
+
// FIX::Group. Walks the two group maps in lockstep (operator= preserves tags,
|
|
161
|
+
// counts and order) and swaps each sliced entry for a typed one that takes
|
|
162
|
+
// over its contents, so nothing is copied twice. The count tag is the map
|
|
163
|
+
// key: a QuickFIX-parsed entry's Group::field() is its first wire tag. The
|
|
164
|
+
// slot keeps its (hollowed) entry until the typed one is complete, so `dst`
|
|
165
|
+
// stays consistent should an allocation throw.
|
|
166
|
+
inline void RebuildGroups(FIX::FieldMap& dst, const FIX::FieldMap& src) {
|
|
167
|
+
FIX::FieldMap::g_iterator copied = dst.g_begin();
|
|
168
|
+
for (const auto& tagWithEntries : src.groups()) {
|
|
169
|
+
const int tag = tagWithEntries.first;
|
|
170
|
+
std::vector<FIX::FieldMap*>& slots = copied->second;
|
|
171
|
+
for (size_t i = 0; i < tagWithEntries.second.size(); i++) {
|
|
172
|
+
const FIX::FieldMap& original = *tagWithEntries.second[i];
|
|
173
|
+
auto typed = std::make_unique<FIX::Group>(tag, DelimOf(original),
|
|
174
|
+
PlaceholderOrder());
|
|
175
|
+
static_cast<FIX::FieldMap&>(*typed) = std::move(*slots[i]);
|
|
176
|
+
RebuildGroups(*typed, original);
|
|
177
|
+
delete slots[i];
|
|
178
|
+
slots[i] = typed.release();
|
|
179
|
+
}
|
|
180
|
+
++copied;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
// Assign `src` to `dst` (fields, nested groups and field order), keeping every
|
|
185
|
+
// nested entry a FIX::Group. `dst`'s own dynamic type is untouched: a Group
|
|
186
|
+
// keeps its count tag and delimiter.
|
|
187
|
+
inline void Clone(FIX::FieldMap& dst, const FIX::FieldMap& src) {
|
|
188
|
+
dst = src;
|
|
189
|
+
RebuildGroups(dst, src);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Header, body and trailer of `src` into `dst`, nested entries typed.
|
|
193
|
+
inline void CloneMessage(FIX::Message& dst, const FIX::Message& src) {
|
|
194
|
+
dst = src;
|
|
195
|
+
RebuildGroups(dst.getHeader(), src.getHeader());
|
|
196
|
+
RebuildGroups(dst, src);
|
|
197
|
+
RebuildGroups(dst.getTrailer(), src.getTrailer());
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// The same, by value. FIX::Message has a move constructor (unlike FIX::Group),
|
|
201
|
+
// so the returned copy stays typed whether or not the return is elided.
|
|
202
|
+
inline FIX::Message CloneMessage(const FIX::Message& src) {
|
|
203
|
+
FIX::Message copy;
|
|
204
|
+
CloneMessage(copy, src);
|
|
205
|
+
return copy;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
// A heap FIX::Group copy of an entry of the `tag` repeating group, for
|
|
209
|
+
// FieldMap::addGroupPtr (which takes ownership).
|
|
210
|
+
inline FIX::Group* CloneEntry(int tag, const FIX::FieldMap& entry) {
|
|
211
|
+
auto group = std::make_unique<FIX::Group>(tag, DelimOf(entry),
|
|
212
|
+
PlaceholderOrder());
|
|
213
|
+
Clone(*group, entry);
|
|
214
|
+
return group.release();
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// `[[tag, value], ...]` for the map's own fields (not its groups' fields).
|
|
218
|
+
inline Napi::Array Fields(Napi::Env env, const FIX::FieldMap& map) {
|
|
219
|
+
Napi::Array out = Napi::Array::New(env);
|
|
220
|
+
uint32_t i = 0;
|
|
221
|
+
for (const FIX::FieldBase& field : map) {
|
|
222
|
+
Napi::Array pair = Napi::Array::New(env, 2);
|
|
223
|
+
pair.Set(0u, Napi::Number::New(env, field.getTag()));
|
|
224
|
+
pair.Set(1u, Napi::String::New(env, field.getString()));
|
|
225
|
+
out.Set(i++, pair);
|
|
226
|
+
}
|
|
227
|
+
return out;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// --- field ops (tag-routed) -------------------------------------------------
|
|
231
|
+
|
|
232
|
+
template <class Select>
|
|
233
|
+
Napi::Value GetField(const Napi::CallbackInfo& info, Select select) {
|
|
234
|
+
Napi::Env env = info.Env();
|
|
235
|
+
int tag = CoerceTag(env, info[0]);
|
|
236
|
+
NQ_TRY(env) {
|
|
237
|
+
return Napi::String::New(env, select(tag).getField(tag));
|
|
238
|
+
} NQ_CATCH(env)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
template <class Select>
|
|
242
|
+
Napi::Value SetField(const Napi::CallbackInfo& info, Select select) {
|
|
243
|
+
Napi::Env env = info.Env();
|
|
244
|
+
int tag = CoerceSetTag(env, info[0]);
|
|
245
|
+
std::string value = CoerceValue(env, info[1]);
|
|
246
|
+
NQ_TRY(env) {
|
|
247
|
+
select(tag).setField(tag, value);
|
|
248
|
+
} NQ_CATCH(env)
|
|
249
|
+
return env.Undefined();
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
template <class Select>
|
|
253
|
+
Napi::Value IsSetField(const Napi::CallbackInfo& info, Select select) {
|
|
254
|
+
Napi::Env env = info.Env();
|
|
255
|
+
int tag = CoerceTag(env, info[0]);
|
|
256
|
+
NQ_TRY(env) {
|
|
257
|
+
return Napi::Boolean::New(env, select(tag).isSetField(tag));
|
|
258
|
+
} NQ_CATCH(env)
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
template <class Select>
|
|
262
|
+
Napi::Value RemoveField(const Napi::CallbackInfo& info, Select select) {
|
|
263
|
+
Napi::Env env = info.Env();
|
|
264
|
+
int tag = CoerceTag(env, info[0]);
|
|
265
|
+
NQ_TRY(env) {
|
|
266
|
+
select(tag).removeField(tag);
|
|
267
|
+
} NQ_CATCH(env)
|
|
268
|
+
return env.Undefined();
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// string | undefined
|
|
272
|
+
template <class Select>
|
|
273
|
+
Napi::Value GetFieldIfSet(const Napi::CallbackInfo& info, Select select) {
|
|
274
|
+
Napi::Env env = info.Env();
|
|
275
|
+
int tag = CoerceTag(env, info[0]);
|
|
276
|
+
NQ_TRY(env) {
|
|
277
|
+
const FIX::FieldMap& map = select(tag);
|
|
278
|
+
if (!map.isSetField(tag)) {
|
|
279
|
+
return env.Undefined();
|
|
280
|
+
}
|
|
281
|
+
return Napi::String::New(env, map.getField(tag));
|
|
282
|
+
} NQ_CATCH(env)
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// --- repeating-group ops (tag-routed) ---------------------------------------
|
|
286
|
+
|
|
287
|
+
// The delimiter of the first typed entry stored under `tag`, 0 when there is
|
|
288
|
+
// none: no entry, or only plain FieldMap entries, whose delimiter is unknown
|
|
289
|
+
// (see DelimOf).
|
|
290
|
+
inline int StoredDelim(const FIX::FieldMap& map, int tag) {
|
|
291
|
+
for (auto groups = map.g_begin(); groups != map.g_end(); ++groups) {
|
|
292
|
+
if (groups->first != tag) {
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
for (const FIX::FieldMap* entry : groups->second) {
|
|
296
|
+
if (const auto* typed = dynamic_cast<const FIX::Group*>(entry)) {
|
|
297
|
+
return typed->delim();
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
return 0;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// addGroup(group: Group): appends a COPY of `group` under its own count tag and
|
|
306
|
+
// bumps the count field. The copy is typed down to the last nested entry (see
|
|
307
|
+
// Clone); the map takes ownership of the pointer.
|
|
308
|
+
// As in ReplaceGroup, an entry whose delimiter differs from the typed entries
|
|
309
|
+
// already stored under the tag is rejected: it would serialize as part of the
|
|
310
|
+
// previous entry, leaving the count field wrong.
|
|
311
|
+
template <class Select>
|
|
312
|
+
Napi::Value AddGroup(const Napi::CallbackInfo& info, Select select) {
|
|
313
|
+
Napi::Env env = info.Env();
|
|
314
|
+
GroupWrap* group = GroupWrap::UnwrapArg(env, info[0], "group");
|
|
315
|
+
const int delim = group->Group().delim();
|
|
316
|
+
int storedDelim = 0;
|
|
317
|
+
NQ_TRY(env) {
|
|
318
|
+
int tag = group->Group().field();
|
|
319
|
+
FIX::FieldMap& map = select(tag);
|
|
320
|
+
storedDelim = StoredDelim(map, tag);
|
|
321
|
+
if (storedDelim == 0 || storedDelim == delim) {
|
|
322
|
+
map.addGroupPtr(tag, CloneEntry(tag, group->Group()), true);
|
|
323
|
+
return env.Undefined();
|
|
324
|
+
}
|
|
325
|
+
} NQ_CATCH(env)
|
|
326
|
+
// Thrown outside NQ_TRY so it stays a TypeError.
|
|
327
|
+
throw Napi::TypeError::New(
|
|
328
|
+
env, "group delimiter " + std::to_string(delim) +
|
|
329
|
+
" does not match the stored entries' delimiter " +
|
|
330
|
+
std::to_string(storedDelim));
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// getGroup(index, tag): Group — a snapshot copy of the index-th entry
|
|
334
|
+
// (1-based). Throws FieldNotFound when the tag has no groups or the index is
|
|
335
|
+
// out of range.
|
|
336
|
+
template <class Select>
|
|
337
|
+
Napi::Value GetGroup(const Napi::CallbackInfo& info, Select select) {
|
|
338
|
+
Napi::Env env = info.Env();
|
|
339
|
+
int index = CoerceIndex(env, info[0]);
|
|
340
|
+
int tag = CoerceTag(env, info[1]);
|
|
341
|
+
NQ_TRY(env) {
|
|
342
|
+
const FIX::FieldMap& stored = select(tag).getGroupRef(index, tag);
|
|
343
|
+
return GroupWrap::NewInstance(env, tag, DelimOf(stored), stored);
|
|
344
|
+
} NQ_CATCH(env)
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// Whether the index-th entry (1-based) exists. QuickFIX's hasGroup(num, tag)
|
|
348
|
+
// is `groupCount(tag) >= num` with no lower bound, so it answers true for
|
|
349
|
+
// index 0 even on an empty tag; 0 is the typo a 1-based API must catch.
|
|
350
|
+
inline bool HasGroupAt(const FIX::FieldMap& map, int index, int tag) {
|
|
351
|
+
return index >= 1 && map.hasGroup(index, tag);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// The stored pointer of the index-th entry (1-based) of `tag`, which must
|
|
355
|
+
// exist (HasGroupAt).
|
|
356
|
+
inline FIX::FieldMap*& SlotOf(FIX::FieldMap& map, int index, int tag) {
|
|
357
|
+
FIX::FieldMap::g_iterator entries = map.g_begin();
|
|
358
|
+
while (entries->first != tag) {
|
|
359
|
+
++entries;
|
|
360
|
+
}
|
|
361
|
+
return entries->second[index - 1];
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// replaceGroup(index, group): swaps the index-th entry (1-based) under
|
|
365
|
+
// group.field() for a typed copy of `group` (FieldMap::replaceGroup would
|
|
366
|
+
// slice the nested entries). The copy is complete before the stored entry is
|
|
367
|
+
// touched, so `group` may contain that entry (`g.replaceGroup(1, g)`).
|
|
368
|
+
// QuickFIX silently ignores a missing slot (including index 0); we throw
|
|
369
|
+
// FieldNotFound instead so a typo cannot silently drop an update.
|
|
370
|
+
// A replacement whose delimiter differs from a typed stored entry's is
|
|
371
|
+
// rejected, as it would not belong to the same repeating group. A plain
|
|
372
|
+
// FieldMap entry (one QuickFIX copied, e.g. a message it queued out of order)
|
|
373
|
+
// has no known delimiter, DelimOf's first-field fallback being a guess, so
|
|
374
|
+
// any replacement is taken.
|
|
375
|
+
template <class Select>
|
|
376
|
+
Napi::Value ReplaceGroup(const Napi::CallbackInfo& info, Select select) {
|
|
377
|
+
Napi::Env env = info.Env();
|
|
378
|
+
int index = CoerceIndex(env, info[0]);
|
|
379
|
+
GroupWrap* group = GroupWrap::UnwrapArg(env, info[1], "group");
|
|
380
|
+
const int delim = group->Group().delim();
|
|
381
|
+
int storedDelim = 0;
|
|
382
|
+
NQ_TRY(env) {
|
|
383
|
+
int tag = group->Group().field();
|
|
384
|
+
FIX::FieldMap& map = select(tag);
|
|
385
|
+
if (!HasGroupAt(map, index, tag)) {
|
|
386
|
+
throw FIX::FieldNotFound(tag);
|
|
387
|
+
}
|
|
388
|
+
FIX::FieldMap*& slot = SlotOf(map, index, tag);
|
|
389
|
+
const auto* typed = dynamic_cast<const FIX::Group*>(slot);
|
|
390
|
+
storedDelim = typed != nullptr ? typed->delim() : delim;
|
|
391
|
+
if (storedDelim == delim) {
|
|
392
|
+
std::unique_ptr<FIX::FieldMap> copy(CloneEntry(tag, group->Group()));
|
|
393
|
+
delete slot;
|
|
394
|
+
slot = copy.release();
|
|
395
|
+
return env.Undefined();
|
|
396
|
+
}
|
|
397
|
+
} NQ_CATCH(env)
|
|
398
|
+
// Thrown outside NQ_TRY so it stays a TypeError.
|
|
399
|
+
throw Napi::TypeError::New(
|
|
400
|
+
env, "group delimiter " + std::to_string(delim) +
|
|
401
|
+
" does not match the stored entry's delimiter " +
|
|
402
|
+
std::to_string(storedDelim));
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// The `(tag)` / `(index, tag)` argument shapes of removeGroup and hasGroup.
|
|
406
|
+
// Coercion happens outside NQ_TRY so a TypeError stays a TypeError (NQ_CATCH
|
|
407
|
+
// would translate it into a plain Error).
|
|
408
|
+
struct IndexAndTag {
|
|
409
|
+
bool hasIndex;
|
|
410
|
+
int index;
|
|
411
|
+
int tag;
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
inline IndexAndTag CoerceIndexAndTag(const Napi::CallbackInfo& info) {
|
|
415
|
+
Napi::Env env = info.Env();
|
|
416
|
+
if (info.Length() < 2 || IsNullish(info[1])) {
|
|
417
|
+
return {false, 0, CoerceTag(env, info[0])};
|
|
418
|
+
}
|
|
419
|
+
int index = CoerceIndex(env, info[0]);
|
|
420
|
+
return {true, index, CoerceTag(env, info[1])};
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// removeGroup(tag) | removeGroup(index, tag). Removing every entry of a tag
|
|
424
|
+
// that has none is a no-op (as QuickFIX); removing a missing entry throws
|
|
425
|
+
// FieldNotFound, as replaceGroup does, where QuickFIX would silently ignore it
|
|
426
|
+
// (index 0, the 0-based typo, included).
|
|
427
|
+
template <class Select>
|
|
428
|
+
Napi::Value RemoveGroup(const Napi::CallbackInfo& info, Select select) {
|
|
429
|
+
Napi::Env env = info.Env();
|
|
430
|
+
const IndexAndTag args = CoerceIndexAndTag(info);
|
|
431
|
+
NQ_TRY(env) {
|
|
432
|
+
FIX::FieldMap& map = select(args.tag);
|
|
433
|
+
if (!args.hasIndex) {
|
|
434
|
+
map.removeGroup(args.tag);
|
|
435
|
+
} else if (HasGroupAt(map, args.index, args.tag)) {
|
|
436
|
+
map.removeGroup(args.index, args.tag);
|
|
437
|
+
} else {
|
|
438
|
+
throw FIX::FieldNotFound(args.tag);
|
|
439
|
+
}
|
|
440
|
+
} NQ_CATCH(env)
|
|
441
|
+
return env.Undefined();
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// hasGroup(tag) | hasGroup(index, tag)
|
|
445
|
+
template <class Select>
|
|
446
|
+
Napi::Value HasGroup(const Napi::CallbackInfo& info, Select select) {
|
|
447
|
+
Napi::Env env = info.Env();
|
|
448
|
+
const IndexAndTag args = CoerceIndexAndTag(info);
|
|
449
|
+
NQ_TRY(env) {
|
|
450
|
+
const FIX::FieldMap& map = select(args.tag);
|
|
451
|
+
return Napi::Boolean::New(
|
|
452
|
+
env, args.hasIndex ? HasGroupAt(map, args.index, args.tag)
|
|
453
|
+
: map.hasGroup(args.tag));
|
|
454
|
+
} NQ_CATCH(env)
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
template <class Select>
|
|
458
|
+
Napi::Value GroupCount(const Napi::CallbackInfo& info, Select select) {
|
|
459
|
+
Napi::Env env = info.Env();
|
|
460
|
+
int tag = CoerceTag(env, info[0]);
|
|
461
|
+
NQ_TRY(env) {
|
|
462
|
+
return Napi::Number::New(
|
|
463
|
+
env, static_cast<double>(select(tag).groupCount(tag)));
|
|
464
|
+
} NQ_CATCH(env)
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// --- whole-map ops ----------------------------------------------------------
|
|
468
|
+
|
|
469
|
+
inline Napi::Value IsEmpty(Napi::Env env, FIX::FieldMap& map) {
|
|
470
|
+
NQ_TRY(env) {
|
|
471
|
+
return Napi::Boolean::New(env, map.isEmpty());
|
|
472
|
+
} NQ_CATCH(env)
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Own fields plus every field of every (nested) group entry.
|
|
476
|
+
inline Napi::Value TotalFields(Napi::Env env, const FIX::FieldMap& map) {
|
|
477
|
+
NQ_TRY(env) {
|
|
478
|
+
return Napi::Number::New(env, static_cast<double>(map.totalFields()));
|
|
479
|
+
} NQ_CATCH(env)
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
inline Napi::Value Clear(Napi::Env env, FIX::FieldMap& map) {
|
|
483
|
+
NQ_TRY(env) {
|
|
484
|
+
map.clear();
|
|
485
|
+
} NQ_CATCH(env)
|
|
486
|
+
return env.Undefined();
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
} // namespace fieldmap
|
|
490
|
+
} // namespace napi_quickfix
|
|
491
|
+
|
|
492
|
+
#endif // NAPI_QUICKFIX_FIELD_MAP_OPS_H
|