@voxgig/sdkgen 4.0.1 → 4.2.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.
@@ -40,8 +40,31 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
40
40
  else ctx.utility.fetcher = makeNetsim(net, testFetcher)
41
41
  }
42
42
 
43
- private def respond(status: Int, data: Object, extra: JMap[String, Object]): JMap[String, Object] = {
44
- val js: Supplier[Object] = () => data
43
+ // THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
44
+ // `transform.res: `body.item`` describes an API that answers {"item": {...}}
45
+ // and the response transform unwraps that key on the way back. Returning the
46
+ // bare payload means the transform unwraps a property that is not there and
47
+ // the caller gets nothing. Mirrors the go/ts/lua/php mocks.
48
+ private def envelope(ctx: Context, data: Object): Object = {
49
+ if (data == null || ctx == null || ctx.point == null) return data
50
+ val tm = Struct.getprop(ctx.point, "transform")
51
+ Struct.getprop(tm, "res") match {
52
+ case spec: String =>
53
+ // Exactly `body.<key>`; a deeper path is not an envelope this mock
54
+ // can synthesise, so it is left alone rather than guessed at.
55
+ if (!spec.startsWith("`body.") || !spec.endsWith("`") || spec.length < 8) return data
56
+ val inner = spec.substring(6, spec.length - 1)
57
+ if (inner.isEmpty || inner.contains(".")) return data
58
+ val wrapped = new java.util.LinkedHashMap[String, Object]()
59
+ wrapped.put(inner, data)
60
+ wrapped
61
+ case _ => data
62
+ }
63
+ }
64
+
65
+ private def respond(ctx: Context, status: Int, data: Object, extra: JMap[String, Object]): JMap[String, Object] = {
66
+ val payload = envelope(ctx, data)
67
+ val js: Supplier[Object] = () => payload
45
68
  val out = new LinkedHashMap[String, Object]()
46
69
  out.put("status", java.lang.Integer.valueOf(status))
47
70
  out.put("statusText", "OK")
@@ -85,18 +108,18 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
85
108
  val args = buildArgs(ctx, op, resolveMatch(ctx, ctx.reqmatch))
86
109
  val found = Struct.select(entmap, args)
87
110
  val ent = Struct.getelem(found, java.lang.Integer.valueOf(0))
88
- if (ent == null) return respond(404, null, extra("statusText", "Not found"))
111
+ if (ent == null) return respond(ctx, 404, null, extra("statusText", "Not found"))
89
112
  Struct.delprop(ent, "$KEY")
90
113
  val out = Struct.clone(ent)
91
- respond(200, out, null)
114
+ respond(ctx, 200, out, null)
92
115
  } else if ("list" == op.name) {
93
116
  val args = buildArgs(ctx, op, ctx.reqmatch)
94
117
  val found = Struct.select(entmap, args)
95
- if (found == null) return respond(404, null, extra("statusText", "Not found"))
118
+ if (found == null) return respond(ctx, 404, null, extra("statusText", "Not found"))
96
119
  val it = found.iterator()
97
120
  while (it.hasNext) Struct.delprop(it.next(), "$KEY")
98
121
  val out = Struct.clone(found)
99
- respond(200, out, null)
122
+ respond(ctx, 200, out, null)
100
123
  } else if ("update" == op.name) {
101
124
  var updateMatch = new LinkedHashMap[String, Object]()
102
125
  if (ctx.reqdata != null) {
@@ -119,11 +142,11 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
119
142
  vit.next() match { case e: JMap[_, _] => ent = e; brk = true; case _ => }
120
143
  }
121
144
  }
122
- if (ent == null) return respond(404, null, extra("statusText", "Not found"))
145
+ if (ent == null) return respond(ctx, 404, null, extra("statusText", "Not found"))
123
146
  ent match { case m: JMap[_, _] if ctx.reqdata != null => m.asInstanceOf[JMap[String, Object]].putAll(ctx.reqdata); case _ => }
124
147
  Struct.delprop(ent, "$KEY")
125
148
  val out = Struct.clone(ent)
126
- respond(200, out, null)
149
+ respond(ctx, 200, out, null)
127
150
  } else if ("remove" == op.name) {
128
151
  val args = buildArgs(ctx, op, resolveMatch(ctx, ctx.reqmatch))
129
152
  val found = Struct.select(entmap, args)
@@ -134,7 +157,7 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
134
157
  Struct.delprop(entmap, id)
135
158
  case _ =>
136
159
  }
137
- respond(200, null, null)
160
+ respond(ctx, 200, null, null)
138
161
  } else if ("create" == op.name) {
139
162
  buildArgs(ctx, op, ctx.reqdata)
140
163
  var id = ctx.utility.param(ctx, "id")
@@ -153,11 +176,11 @@ class TestFeature extends BaseFeature("test", "0.0.1", true) {
153
176
  id match { case s: String => entmap.put(s, entm); case _ => }
154
177
  Struct.delprop(entm, "$KEY")
155
178
  val out = Struct.clone(entm)
156
- respond(200, out, null)
157
- case _ => respond(200, ent, null)
179
+ respond(ctx, 200, out, null)
180
+ case _ => respond(ctx, 200, ent, null)
158
181
  }
159
182
  } else {
160
- respond(404, null, extra("statusText", "Unknown operation"))
183
+ respond(ctx, 404, null, extra("statusText", "Unknown operation"))
161
184
  }
162
185
  }
163
186
 
@@ -5,11 +5,32 @@
5
5
 
6
6
  import Foundation
7
7
 
8
- private func testRespond(_ status: Int, _ data: Value, _ extra: VMap?) -> Value {
8
+ // THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
9
+ // `transform.res: `body.item`` describes an API that answers {"item": {...}},
10
+ // and the response transform unwraps that key on the way back. Returning the
11
+ // bare payload means the transform unwraps a property that is not there and
12
+ // the caller gets nothing. Mirrors the go/ts/lua/php mocks.
13
+ private func testEnvelope(_ ctx: Context, _ data: Value) -> Value {
14
+ if case .noval = data { return data }
15
+ if case .null = data { return data }
16
+ guard let point = ctx.point else { return data }
17
+ guard case .map(let tm)? = point.entries["transform"] else { return data }
18
+ guard case .string(let spec)? = tm.entries["res"] else { return data }
19
+ // Exactly `body.<key>`; a deeper path is not an envelope this mock can
20
+ // synthesise, so it is left alone rather than guessed at.
21
+ guard spec.hasPrefix("`body."), spec.hasSuffix("`"), spec.count >= 8 else { return data }
22
+ let inner = String(spec.dropFirst(6).dropLast(1))
23
+ guard !inner.isEmpty, !inner.contains(".") else { return data }
24
+ let wrapped = VMap()
25
+ wrapped.entries[inner] = data
26
+ return .map(wrapped)
27
+ }
28
+
29
+ private func testRespond(_ ctx: Context, _ status: Int, _ data: Value, _ extra: VMap?) -> Value {
9
30
  let res = VMap()
10
31
  res.entries["status"] = .int(Int64(status))
11
32
  res.entries["statusText"] = .string("OK")
12
- let captured = data
33
+ let captured = testEnvelope(ctx, data)
13
34
  res.entries["json"] = .nat({ () -> Value in captured } as NativeCall0)
14
35
  res.entries["body"] = .string("not-used")
15
36
  if let extra = extra {
@@ -151,22 +172,22 @@ public final class TestFeature: BaseFeature {
151
172
  if isNil(ent) {
152
173
  let extra = VMap()
153
174
  extra.entries["statusText"] = .string("Not found")
154
- return testRespond(404, .noval, extra)
175
+ return testRespond(ctx2, 404, .noval, extra)
155
176
  }
156
177
  delprop(ent, .string("$KEY"))
157
- return testRespond(200, clone(ent), nil)
178
+ return testRespond(ctx2, 200, clone(ent), nil)
158
179
  } else if op.name == "list" {
159
180
  let args = testBuildArgs(ctx2, op, ctx2.reqmatch)
160
181
  let found = select(.map(entmap), args)
161
182
  if isNil(found) {
162
183
  let extra = VMap()
163
184
  extra.entries["statusText"] = .string("Not found")
164
- return testRespond(404, .noval, extra)
185
+ return testRespond(ctx2, 404, .noval, extra)
165
186
  }
166
187
  if let fl = found.asList {
167
188
  for item in fl.items { delprop(item, .string("$KEY")) }
168
189
  }
169
- return testRespond(200, clone(found), nil)
190
+ return testRespond(ctx2, 200, clone(found), nil)
170
191
  } else if op.name == "update" {
171
192
  var updateMatch = VMap()
172
193
  if let idv = ctx2.reqdata.entries["id"] {
@@ -188,13 +209,13 @@ public final class TestFeature: BaseFeature {
188
209
  if isNil(ent) {
189
210
  let extra = VMap()
190
211
  extra.entries["statusText"] = .string("Not found")
191
- return testRespond(404, .noval, extra)
212
+ return testRespond(ctx2, 404, .noval, extra)
192
213
  }
193
214
  if let entm = ent.asMap {
194
215
  for (k, v) in ctx2.reqdata.entries { entm.entries[k] = v }
195
216
  }
196
217
  delprop(ent, .string("$KEY"))
197
- return testRespond(200, clone(ent), nil)
218
+ return testRespond(ctx2, 200, clone(ent), nil)
198
219
  } else if op.name == "remove" {
199
220
  let args = testBuildArgs(ctx2, op, testResolveMatch(ctx2, ctx2.reqmatch))
200
221
  let found = select(.map(entmap), args)
@@ -203,7 +224,7 @@ public final class TestFeature: BaseFeature {
203
224
  let id = gp(entm2, "id")
204
225
  delprop(.map(entmap), id)
205
226
  }
206
- return testRespond(200, .noval, nil)
227
+ return testRespond(ctx2, 200, .noval, nil)
207
228
  } else if op.name == "create" {
208
229
  _ = testBuildArgs(ctx2, op, ctx2.reqdata)
209
230
  var id = ctx2.utility!.param(ctx2, .string("id"))
@@ -218,14 +239,14 @@ public final class TestFeature: BaseFeature {
218
239
  entm.entries["id"] = id
219
240
  if let idStr = id.asString { entmap.entries[idStr] = .map(entm) }
220
241
  delprop(.map(entm), .string("$KEY"))
221
- return testRespond(200, clone(.map(entm)), nil)
242
+ return testRespond(ctx2, 200, clone(.map(entm)), nil)
222
243
  }
223
- return testRespond(200, ent, nil)
244
+ return testRespond(ctx2, 200, ent, nil)
224
245
  }
225
246
 
226
247
  let extra = VMap()
227
248
  extra.entries["statusText"] = .string("Unknown operation")
228
- return testRespond(404, .noval, extra)
249
+ return testRespond(ctx2, 404, .noval, extra)
229
250
  }
230
251
 
231
252
  // Optional network behaviour simulation over the mock transport.
@@ -93,11 +93,38 @@ fn fixIds(_: Allocator, key: ?[]const u8, val: Value, _: Value, path: []const []
93
93
  return val;
94
94
  }
95
95
 
96
- fn respond(status: i64, data: Value, extra: []const h.Pair) Value {
96
+ // THE MOCK HAS TO AGREE WITH THE MODEL.
97
+ //
98
+ // A point carrying `transform.res: `body.item`` describes an API that answers
99
+ // {"item": {...}}, and the response transform unwraps that key on the way
100
+ // back. Handing back the bare payload means the transform unwraps a property
101
+ // that is not there and the caller gets nothing — a mock that only ever
102
+ // simulates APIs whose responses happen to be unwrapped.
103
+ //
104
+ // univec's list op declares `body.data`, so every list returned zero items
105
+ // while the fixture plainly held two. Mirrors the go/ts/lua/php mocks, which
106
+ // already wrap; rust, c and zig were the three that did not.
107
+ fn envelope(ctx: *Context, data: Value) Value {
108
+ if (data == .undef or data == .null) return data;
109
+ const restf = h.getpath(&.{ "transform", "res" }, ctx.point);
110
+ if (restf != .string) return data;
111
+ const spec = restf.string;
112
+ // Exactly `body.<key>`; a deeper path is not an envelope this mock can
113
+ // synthesise, so it is left alone rather than guessed at.
114
+ if (spec.len < 8) return data;
115
+ if (!std.mem.startsWith(u8, spec, "`body.")) return data;
116
+ if (!std.mem.endsWith(u8, spec, "`")) return data;
117
+ const inner = spec[6 .. spec.len - 1];
118
+ if (inner.len == 0) return data;
119
+ if (std.mem.indexOfScalar(u8, inner, '.') != null) return data;
120
+ return h.jo(&.{.{ inner, data }});
121
+ }
122
+
123
+ fn respond(ctx: *Context, status: i64, data: Value, extra: []const h.Pair) Value {
97
124
  const out = h.jo(&.{
98
125
  .{ "status", h.vnum(status) },
99
126
  .{ "statusText", h.vstr("OK") },
100
- .{ "json", h.json_thunk(data) },
127
+ .{ "json", h.json_thunk(envelope(ctx, data)) },
101
128
  .{ "body", h.vstr("not-used") },
102
129
  });
103
130
  for (extra) |kv| h.setp(out, kv[0], kv[1]);
@@ -182,20 +209,20 @@ fn test_fetch(entity: Value, ctx: *Context, _: []const u8, _: Value) err.E!Value
182
209
  const found = vs.select(h.A(), entmap, args) catch h.olist();
183
210
  const ent = h.get_elem(found, h.vnum(0), h.vnull());
184
211
  if (h.is_noval(ent)) {
185
- return respond(404, h.vnull(), &.{.{ "statusText", h.vstr("Not found") }});
212
+ return respond(ctx, 404, h.vnull(), &.{.{ "statusText", h.vstr("Not found") }});
186
213
  }
187
214
  h.del_prop(ent, h.vstr("$KEY"));
188
- return respond(200, h.clone(ent), &.{});
215
+ return respond(ctx, 200, h.clone(ent), &.{});
189
216
  } else if (std.mem.eql(u8, op.name, "list")) {
190
217
  const args = build_args(ctx, ctx.reqmatch);
191
218
  const found = vs.select(h.A(), entmap, args) catch h.olist();
192
219
  if (h.is_noval(found)) {
193
- return respond(404, h.vnull(), &.{.{ "statusText", h.vstr("Not found") }});
220
+ return respond(ctx, 404, h.vnull(), &.{.{ "statusText", h.vstr("Not found") }});
194
221
  }
195
222
  if (found == .array) {
196
223
  for (found.array.data.items) |item| h.del_prop(item, h.vstr("$KEY"));
197
224
  }
198
- return respond(200, h.clone(found), &.{});
225
+ return respond(ctx, 200, h.clone(found), &.{});
199
226
  } else if (std.mem.eql(u8, op.name, "update")) {
200
227
  const reqdata = ctx.reqdata;
201
228
  var update_match = h.omap();
@@ -221,14 +248,14 @@ fn test_fetch(entity: Value, ctx: *Context, _: []const u8, _: Value) err.E!Value
221
248
  }
222
249
  }
223
250
  if (h.is_noval(ent)) {
224
- return respond(404, h.vnull(), &.{.{ "statusText", h.vstr("Not found") }});
251
+ return respond(ctx, 404, h.vnull(), &.{.{ "statusText", h.vstr("Not found") }});
225
252
  }
226
253
  if (ent == .object and reqdata == .object) {
227
254
  var it = reqdata.object.iterator();
228
255
  while (it.next()) |kv| h.setp(ent, kv.key_ptr.*, kv.value_ptr.*);
229
256
  }
230
257
  h.del_prop(ent, h.vstr("$KEY"));
231
- return respond(200, h.clone(ent), &.{});
258
+ return respond(ctx, 200, h.clone(ent), &.{});
232
259
  } else if (std.mem.eql(u8, op.name, "remove")) {
233
260
  const m = resolve_match(ctx, ctx.reqmatch);
234
261
  const args = build_args(ctx, m);
@@ -238,7 +265,7 @@ fn test_fetch(entity: Value, ctx: *Context, _: []const u8, _: Value) err.E!Value
238
265
  const id = h.getp(ent, "id");
239
266
  h.del_prop(entmap, id);
240
267
  }
241
- return respond(200, h.vnull(), &.{});
268
+ return respond(ctx, 200, h.vnull(), &.{});
242
269
  } else if (std.mem.eql(u8, op.name, "create")) {
243
270
  _ = build_args(ctx, ctx.reqdata);
244
271
  var id = ctx.util().param(ctx, h.vstr("id"));
@@ -255,12 +282,12 @@ fn test_fetch(entity: Value, ctx: *Context, _: []const u8, _: Value) err.E!Value
255
282
  h.setp(ent, "id", id);
256
283
  if (id == .string) h.setp(entmap, id.string, ent);
257
284
  h.del_prop(ent, h.vstr("$KEY"));
258
- return respond(200, h.clone(ent), &.{});
285
+ return respond(ctx, 200, h.clone(ent), &.{});
259
286
  }
260
- return respond(200, ent, &.{});
287
+ return respond(ctx, 200, ent, &.{});
261
288
  }
262
289
 
263
- return respond(404, h.vnull(), &.{.{ "statusText", h.vstr("Unknown operation") }});
290
+ return respond(ctx, 404, h.vnull(), &.{.{ "statusText", h.vstr("Unknown operation") }});
264
291
  }
265
292
 
266
293
  // make_netsim (test-local): counter-driven latency / first-N failures /
@@ -3,7 +3,7 @@
3
3
  "package": 1
4
4
  },
5
5
  "name": "@voxgig/sdkgen",
6
- "version": "4.0.1",
6
+ "version": "4.2.0",
7
7
  "provides": {
8
8
  "target": [
9
9
  "c",