@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.
package/bin/voxgig-sdkgen CHANGED
@@ -8,7 +8,7 @@ const { Shape, One, Skip } = require('shape')
8
8
 
9
9
  const { SdkGen } = require('../dist/sdkgen.js')
10
10
 
11
- const VERSION = '4.0.1'
11
+ const VERSION = '4.2.0'
12
12
  const KONSOLE = console
13
13
 
14
14
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@voxgig/sdkgen",
3
- "version": "4.0.1",
3
+ "version": "4.2.0",
4
4
  "main": "dist/sdkgen.js",
5
5
  "type": "commonjs",
6
6
  "engines": {
@@ -118,7 +118,7 @@ const TestDirect = cmp(function TestDirect(props: any) {
118
118
  import java.util.{ArrayList, LinkedHashMap, List => JList, Map => JMap}
119
119
  import java.util.function.{BiFunction, Supplier}
120
120
 
121
- import ${scalapackage}.core.{Helpers, ${SDK}}
121
+ import ${scalapackage}.core.{Helpers, SdkEntity, ${SDK}}
122
122
 
123
123
  object ${EntityName}DirectTest {
124
124
 
@@ -90,7 +90,7 @@ const TestEntity = cmp(function TestEntity(props: any) {
90
90
 
91
91
  import java.util.{ArrayList, LinkedHashMap, List => JList, Map => JMap}
92
92
 
93
- import ${scalapackage}.core.{Helpers, ${SDK}}
93
+ import ${scalapackage}.core.{Helpers, SdkEntity, ${SDK}}
94
94
  import ${scalapackage}.utility.struct.Struct
95
95
 
96
96
  object ${EntityName}EntityTest {
@@ -10,10 +10,48 @@
10
10
  #include <stdlib.h>
11
11
  #include <string.h>
12
12
 
13
+ // THE MOCK HAS TO AGREE WITH THE MODEL.
14
+ //
15
+ // A point carrying `transform.res: `body.item`` describes an API that answers
16
+ // {"item": {...}}, and the response transform unwraps that key on the way
17
+ // back. Handing back the bare payload means the transform unwraps a property
18
+ // that is not there and the caller gets nothing — a mock that only ever
19
+ // simulates APIs whose responses happen to be unwrapped.
20
+ //
21
+ // univec's list op declares `body.data`, so every list returned zero items
22
+ // while the fixture plainly held two. Mirrors the go/ts/lua/php mocks, which
23
+ // already wrap; rust, c and zig were the three that did not.
24
+ static voxgig_value* envelope(Context* ctx, voxgig_value* data) {
25
+ if (v_is_noval(data) || v_is_null(data)) {
26
+ return data;
27
+ }
28
+ voxgig_value* tm = getp(ctx->point, "transform");
29
+ const char* spec = get_str(tm, "res");
30
+ if (NULL == spec) {
31
+ return data;
32
+ }
33
+ // Exactly `body.<key>`; a deeper path is not an envelope this mock can
34
+ // synthesise, so it is left alone rather than guessed at.
35
+ size_t n = strlen(spec);
36
+ if (n < 8 || 0 != strncmp(spec, "`body.", 6) || '`' != spec[n - 1]) {
37
+ return data;
38
+ }
39
+ size_t inner_len = n - 7;
40
+ if (0 == inner_len || NULL != memchr(spec + 6, '.', inner_len)) {
41
+ return data;
42
+ }
43
+ char* inner = (char*)malloc(inner_len + 1);
44
+ memcpy(inner, spec + 6, inner_len);
45
+ inner[inner_len] = '\0';
46
+ voxgig_value* out = cmap(1, inner, data);
47
+ free(inner);
48
+ return out;
49
+ }
50
+
13
51
  // respond builds a transport-shaped response the result pipeline understands.
14
- static voxgig_value* respond(int64_t status, voxgig_value* data) {
52
+ static voxgig_value* respond(Context* ctx, int64_t status, voxgig_value* data) {
15
53
  return cmap(4, "status", v_num((double)status), "statusText", v_str("OK"), "json",
16
- json_thunk(data), "body", v_str("not-used"));
54
+ json_thunk(envelope(ctx, data)), "body", v_str("not-used"));
17
55
  }
18
56
 
19
57
  // For single-entity ops (load, remove) with an empty explicit match, fall
@@ -109,19 +147,19 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
109
147
  voxgig_value* found = voxgig_select(entmap, args);
110
148
  voxgig_value* ent = voxgig_getelem(found, v_int(0), NULL);
111
149
  if (v_is_noval(ent) || v_is_null(ent)) {
112
- voxgig_value* r = respond(404, v_undef());
150
+ voxgig_value* r = respond(ctx, 404, v_undef());
113
151
  setp(r, "statusText", v_str("Not found"));
114
152
  return r;
115
153
  }
116
154
  voxgig_delprop(ent, v_str("$KEY"));
117
- return respond(200, voxgig_clone(ent));
155
+ return respond(ctx, 200, voxgig_clone(ent));
118
156
  }
119
157
 
120
158
  if (strcmp(opname, "list") == 0) {
121
159
  voxgig_value* args = build_args(ctx, ctx->reqmatch);
122
160
  voxgig_value* found = voxgig_select(entmap, args);
123
161
  if (v_is_noval(found) || v_is_null(found)) {
124
- voxgig_value* r = respond(404, v_undef());
162
+ voxgig_value* r = respond(ctx, 404, v_undef());
125
163
  setp(r, "statusText", v_str("Not found"));
126
164
  return r;
127
165
  }
@@ -131,7 +169,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
131
169
  voxgig_delprop(l->items[i], v_str("$KEY"));
132
170
  }
133
171
  }
134
- return respond(200, voxgig_clone(found));
172
+ return respond(ctx, 200, voxgig_clone(found));
135
173
  }
136
174
 
137
175
  if (strcmp(opname, "update") == 0) {
@@ -168,7 +206,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
168
206
  }
169
207
  }
170
208
  if (v_is_noval(ent) || v_is_null(ent)) {
171
- voxgig_value* r = respond(404, v_undef());
209
+ voxgig_value* r = respond(ctx, 404, v_undef());
172
210
  setp(r, "statusText", v_str("Not found"));
173
211
  return r;
174
212
  }
@@ -179,7 +217,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
179
217
  }
180
218
  }
181
219
  voxgig_delprop(ent, v_str("$KEY"));
182
- return respond(200, voxgig_clone(ent));
220
+ return respond(ctx, 200, voxgig_clone(ent));
183
221
  }
184
222
 
185
223
  if (strcmp(opname, "remove") == 0) {
@@ -192,7 +230,7 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
192
230
  voxgig_value* id = getp(ent, "id");
193
231
  voxgig_delprop(entmap, id);
194
232
  }
195
- return respond(200, v_undef());
233
+ return respond(ctx, 200, v_undef());
196
234
  }
197
235
 
198
236
  if (strcmp(opname, "create") == 0) {
@@ -216,12 +254,12 @@ static voxgig_value* test_fetch(voxgig_value* entity, Context* ctx, const char*
216
254
  setp(entmap, voxgig_as_string(id), ent);
217
255
  }
218
256
  voxgig_delprop(ent, v_str("$KEY"));
219
- return respond(200, voxgig_clone(ent));
257
+ return respond(ctx, 200, voxgig_clone(ent));
220
258
  }
221
- return respond(200, ent);
259
+ return respond(ctx, 200, ent);
222
260
  }
223
261
 
224
- voxgig_value* r = respond(404, v_undef());
262
+ voxgig_value* r = respond(ctx, 404, v_undef());
225
263
  setp(r, "statusText", v_str("Unknown operation"));
226
264
  return r;
227
265
  }
@@ -104,9 +104,27 @@
104
104
  val))
105
105
  (let [test-fetcher
106
106
  (fn [fctx _fullurl _fetchdef]
107
- (let [respond (fn [status data extra]
108
- (let [out (vs/jm "status" status "statusText" "OK"
109
- "json" (fn [] data) "body" "not-used")]
107
+ ;; THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
108
+ ;; `transform.res: `body.item`` describes an API that answers
109
+ ;; {"item": {...}}, and the response transform unwraps that key
110
+ ;; on the way back. Returning the bare payload means the
111
+ ;; transform unwraps a property that is not there and the caller
112
+ ;; gets nothing. Mirrors the go/ts/lua/php mocks. `fctx` is in
113
+ ;; scope here, so the point is the one being served.
114
+ (let [envelope (fn [data]
115
+ (let [tm (vs/getprop (core/oget fctx :point) "transform")
116
+ spec (vs/getprop tm "res")]
117
+ ;; Exactly `body.<key>`; a deeper path is not an
118
+ ;; envelope this mock can synthesise.
119
+ (if (and (some? data) (string? spec))
120
+ (if-let [m (re-matches #"`body\.([^`.]+)`" spec)]
121
+ (vs/jm (second m) data)
122
+ data)
123
+ data)))
124
+ respond (fn [status data extra]
125
+ (let [payload (envelope data)
126
+ out (vs/jm "status" status "statusText" "OK"
127
+ "json" (fn [] payload) "body" "not-used")]
110
128
  (when extra (doseq [item (or (vs/items extra) [])]
111
129
  (.put ^java.util.Map out (vs/getprop item 0) (vs/getprop item 1))))
112
130
  [out nil]))
@@ -10,6 +10,16 @@ CXXFLAGS ?= -std=c++17 -O0 -g -Wall -Wextra -Wno-unused-parameter -pthread
10
10
  TEST_SRCS := $(wildcard test/*.cpp)
11
11
  TEST_BINS := $(TEST_SRCS:.cpp=.out)
12
12
 
13
+ # REBUILD WHEN A HEADER CHANGES. The runtime is header-only, so a test binary
14
+ # depends on far more than its own .cpp — but the pattern rule below names
15
+ # only that .cpp, so make happily reused a stale .out after every change to
16
+ # core/, feature/, entity/ or utility/. A real fix to the mock transport read
17
+ # as "still failing" through several rebuilds because the binary under test
18
+ # predated it. Listing the headers is cheap and makes `make test` mean what it
19
+ # says.
20
+ SDK_HDRS := $(wildcard core/*.hpp entity/*.hpp feature/*.hpp \
21
+ utility/*.hpp utility/*/*.hpp *.hpp)
22
+
13
23
  .PHONY: test build clean
14
24
 
15
25
  build: $(TEST_BINS)
@@ -23,7 +33,7 @@ test: build
23
33
  if [ $$fail -ne 0 ]; then echo "SOME TESTS FAILED"; exit 1; fi; \
24
34
  echo "ALL TESTS PASSED"
25
35
 
26
- test/%.out: test/%.cpp
36
+ test/%.out: test/%.cpp $(SDK_HDRS)
27
37
  $(CXX) $(CXXFLAGS) $< -o $@
28
38
 
29
39
  clean:
@@ -57,11 +57,34 @@ public:
57
57
  }
58
58
 
59
59
  private:
60
- Value respond(int status, const Value& data, const Value& extra) {
60
+ // THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
61
+ // `transform.res: `body.item`` describes an API that answers {"item": {...}}
62
+ // and the response transform unwraps that key on the way back. Returning the
63
+ // bare payload means the transform unwraps a property that is not there and
64
+ // the caller gets nothing. Mirrors the go/ts/lua/php mocks.
65
+ Value envelope(CtxPtr ctx, const Value& data) {
66
+ if (is_nullish(data) || !ctx) return data;
67
+ Value tm = getp(ctx->point, "transform");
68
+ Value restf = getp(tm, "res");
69
+ if (!restf.is_string()) return data;
70
+ std::string spec = restf.as_string();
71
+ // Exactly `body.<key>`; a deeper path is not an envelope this mock can
72
+ // synthesise, so it is left alone rather than guessed at.
73
+ if (spec.size() < 8) return data;
74
+ if (0 != spec.compare(0, 6, "`body.")) return data;
75
+ if ('`' != spec[spec.size() - 1]) return data;
76
+ std::string inner = spec.substr(6, spec.size() - 7);
77
+ if (inner.empty() || std::string::npos != inner.find('.')) return data;
78
+ Value wrapped = vmap();
79
+ map_put(wrapped, inner, data);
80
+ return wrapped;
81
+ }
82
+
83
+ Value respond(CtxPtr ctx, int status, const Value& data, const Value& extra) {
61
84
  Value out = vmap();
62
85
  map_put(out, "status", Value(status));
63
86
  map_put(out, "statusText", Value("OK"));
64
- map_put(out, "json", json_thunk(data));
87
+ map_put(out, "json", json_thunk(envelope(ctx, data)));
65
88
  map_put(out, "body", Value("not-used"));
66
89
  if (extra.is_map()) {
67
90
  for (const auto& kv : *extra.as_map()) map_put(out, kv.first, kv.second);
@@ -100,10 +123,10 @@ private:
100
123
  Value args = buildArgs(ctx, op, resolveMatch(ctx, ctx->reqmatch));
101
124
  std::vector<Value> found = Struct::select(entmap, args);
102
125
  Value ent = found.empty() ? Value::undef() : found[0];
103
- if (is_nullish(ent)) return respond(404, Value(nullptr), extra1("statusText", Value("Not found")));
126
+ if (is_nullish(ent)) return respond(ctx, 404, Value(nullptr), extra1("statusText", Value("Not found")));
104
127
  Struct::delprop(ent, Value("$KEY"));
105
128
  Value out = Struct::clone(ent);
106
- return respond(200, out, Value::undef());
129
+ return respond(ctx, 200, out, Value::undef());
107
130
  } else if (op->name == "list") {
108
131
  Value args = buildArgs(ctx, op, ctx->reqmatch);
109
132
  std::vector<Value> found = Struct::select(entmap, args);
@@ -113,7 +136,7 @@ private:
113
136
  outlist.as_list()->push_back(item);
114
137
  }
115
138
  Value out = Struct::clone(outlist);
116
- return respond(200, out, Value::undef());
139
+ return respond(ctx, 200, out, Value::undef());
117
140
  } else if (op->name == "update") {
118
141
  Value updateMatch = vmap();
119
142
  if (ctx->reqdata.is_map()) {
@@ -141,13 +164,13 @@ private:
141
164
  if (kv.second.is_map()) { ent = kv.second; break; }
142
165
  }
143
166
  }
144
- if (is_nullish(ent)) return respond(404, Value(nullptr), extra1("statusText", Value("Not found")));
167
+ if (is_nullish(ent)) return respond(ctx, 404, Value(nullptr), extra1("statusText", Value("Not found")));
145
168
  if (ent.is_map() && ctx->reqdata.is_map()) {
146
169
  for (const auto& kv : *ctx->reqdata.as_map()) map_put(ent, kv.first, kv.second);
147
170
  }
148
171
  Struct::delprop(ent, Value("$KEY"));
149
172
  Value out = Struct::clone(ent);
150
- return respond(200, out, Value::undef());
173
+ return respond(ctx, 200, out, Value::undef());
151
174
  } else if (op->name == "remove") {
152
175
  Value args = buildArgs(ctx, op, resolveMatch(ctx, ctx->reqmatch));
153
176
  std::vector<Value> found = Struct::select(entmap, args);
@@ -156,7 +179,7 @@ private:
156
179
  Value id = getp(ent, "id", Value(nullptr));
157
180
  Struct::delprop(entmap, id);
158
181
  }
159
- return respond(200, Value(nullptr), Value::undef());
182
+ return respond(ctx, 200, Value(nullptr), Value::undef());
160
183
  } else if (op->name == "create") {
161
184
  buildArgs(ctx, op, ctx->reqdata);
162
185
  Value id = ctx->utility->param(ctx, Value("id"));
@@ -177,12 +200,12 @@ private:
177
200
  if (id.is_string()) map_put(entmap, id.as_string(), ent);
178
201
  Struct::delprop(ent, Value("$KEY"));
179
202
  Value out = Struct::clone(ent);
180
- return respond(200, out, Value::undef());
203
+ return respond(ctx, 200, out, Value::undef());
181
204
  }
182
- return respond(200, ent, Value::undef());
205
+ return respond(ctx, 200, ent, Value::undef());
183
206
  }
184
207
 
185
- return respond(404, Value(nullptr), extra1("statusText", Value("Unknown operation")));
208
+ return respond(ctx, 404, Value(nullptr), extra1("statusText", Value("Unknown operation")));
186
209
  }
187
210
 
188
211
  // ---- net simulation over the mock -----------------------------------
@@ -45,14 +45,40 @@ public class TestFeature : BaseFeature
45
45
 
46
46
  FetcherFunc testFetcher = (ctx2, _fullurl, _fetchdef) =>
47
47
  {
48
- static Dictionary<string, object?> Respond(int status, object? data,
48
+ // THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
49
+ // `transform.res: `body.item`` describes an API that answers
50
+ // {"item": {...}}, and the response transform unwraps that key on
51
+ // the way back. Returning the bare payload means the transform
52
+ // unwraps a property that is not there and the caller gets
53
+ // nothing. Mirrors the go/ts/lua/php mocks.
54
+ //
55
+ // NOT `static`: it closes over ctx2 to read the point, which is
56
+ // how go's mock does it too.
57
+ object? Envelope(object? data)
58
+ {
59
+ if (data == null || ctx2.Point == null) { return data; }
60
+ var tm = StructUtils.GetProp(ctx2.Point, "transform");
61
+ if (StructUtils.GetProp(tm, "res") is not string spec) { return data; }
62
+ // Exactly `body.<key>`; a deeper path is not an envelope this
63
+ // mock can synthesise, so it is left alone.
64
+ if (!spec.StartsWith("`body.") || !spec.EndsWith("`") || spec.Length < 8)
65
+ {
66
+ return data;
67
+ }
68
+ var inner = spec.Substring(6, spec.Length - 7);
69
+ if (inner.Length == 0 || inner.Contains('.')) { return data; }
70
+ return new Dictionary<string, object?> { [inner] = data };
71
+ }
72
+
73
+ Dictionary<string, object?> Respond(int status, object? data,
49
74
  Dictionary<string, object?>? extra)
50
75
  {
76
+ var payload = Envelope(data);
51
77
  var res = new Dictionary<string, object?>
52
78
  {
53
79
  ["status"] = status,
54
80
  ["statusText"] = "OK",
55
- ["json"] = (Func<object?>)(() => data),
81
+ ["json"] = (Func<object?>)(() => payload),
56
82
  ["body"] = "not-used",
57
83
  };
58
84
  if (extra != null)
@@ -38,12 +38,40 @@ class TestFeature extends BaseFeature {
38
38
 
39
39
  final self = this;
40
40
 
41
- dynamic respond(int status, [dynamic data, dynamic res]) {
41
+ // THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
42
+ // `transform.res: `body.item`` describes an API that answers
43
+ // {"item": {...}}, and the response transform unwraps that key on the way
44
+ // back. Returning the bare payload means the transform unwraps a property
45
+ // that is not there and the caller gets nothing. Mirrors the go/ts/lua/php
46
+ // mocks.
47
+ dynamic envelope(dynamic fctx, dynamic data) {
48
+ if (null == data || null == fctx || null == fctx.point) {
49
+ return data;
50
+ }
51
+ // dart wraps the point in a Point OBJECT (Context._aspoint), so its
52
+ // fields are typed properties, not map keys — vs.getprop on it returns
53
+ // null and the envelope silently never applies.
54
+ final tm = fctx.point.transform;
55
+ final spec = vs.getprop(tm, 'res');
56
+ if (spec is! String) {
57
+ return data;
58
+ }
59
+ // Exactly `body.<key>`; a deeper path is not an envelope this mock can
60
+ // synthesise, so it is left alone rather than guessed at.
61
+ final m = RegExp(r'^`body\.([^`.]+)`$').firstMatch(spec);
62
+ if (null == m) {
63
+ return data;
64
+ }
65
+ return <String, dynamic>{m.group(1)!: data};
66
+ }
67
+
68
+ dynamic respond(dynamic fctx, int status, [dynamic data, dynamic res]) {
69
+ final payload = envelope(fctx, data);
42
70
  final out = vs.merge([
43
71
  <String, dynamic>{
44
72
  'status': status,
45
73
  'statusText': 'OK',
46
- 'json': () => data,
74
+ 'json': () => payload,
47
75
  'body': 'not-used',
48
76
  },
49
77
  vs.getdef(res, {}),
@@ -66,30 +94,30 @@ class TestFeature extends BaseFeature {
66
94
  final found = vs.select(entmap, args);
67
95
  final ent = vs.getelem(found, 0);
68
96
  if (null == ent) {
69
- return respond(404, null, {'statusText': S_NOT_FOUND});
97
+ return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
70
98
  } else {
71
99
  vs.delprop(ent, r'$KEY');
72
100
  final out = vs.clone(ent);
73
- return respond(200, out);
101
+ return respond(fctx, 200, out);
74
102
  }
75
103
  } else if ('list' == op.name) {
76
104
  final args = self.buildArgs(fctx, op, fctx.reqmatch);
77
105
  final found = vs.select(entmap, args);
78
106
  if (null == found) {
79
- return respond(404, null, {'statusText': S_NOT_FOUND});
107
+ return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
80
108
  } else {
81
109
  for (final ent in found) {
82
110
  vs.delprop(ent, r'$KEY');
83
111
  }
84
112
  final out = vs.clone(found);
85
- return respond(200, out);
113
+ return respond(fctx, 200, out);
86
114
  }
87
115
  } else if ('update' == op.name) {
88
116
  final args = self.buildArgs(fctx, op, fctx.reqdata);
89
117
  final found = vs.select(entmap, args);
90
118
  final ent = vs.getelem(found, 0);
91
119
  if (null == ent) {
92
- return respond(404, null, {'statusText': S_NOT_FOUND});
120
+ return respond(fctx, 404, null, {'statusText': S_NOT_FOUND});
93
121
  } else {
94
122
  // Dart's single null stands in for the donor's undefined: merge
95
123
  // must not overwrite stored values with absent ones.
@@ -104,7 +132,7 @@ class TestFeature extends BaseFeature {
104
132
  vs.merge([ent, upddata]);
105
133
  vs.delprop(ent, r'$KEY');
106
134
  final out = vs.clone(ent);
107
- return respond(200, out);
135
+ return respond(fctx, 200, out);
108
136
  }
109
137
  } else if ('remove' == op.name) {
110
138
  final args = self.buildArgs(fctx, op, fctx.reqmatch);
@@ -115,7 +143,7 @@ class TestFeature extends BaseFeature {
115
143
  if (null != ent) {
116
144
  vs.delprop(entmap, vs.getprop(ent, 'id'));
117
145
  }
118
- return respond(200);
146
+ return respond(fctx, 200);
119
147
  } else if ('create' == op.name) {
120
148
  self.buildArgs(fctx, op, fctx.reqdata);
121
149
  dynamic id = param(fctx, 'id');
@@ -130,7 +158,7 @@ class TestFeature extends BaseFeature {
130
158
  vs.setprop(entmap, id, ent);
131
159
  vs.delprop(ent, r'$KEY');
132
160
  final out = vs.clone(ent);
133
- return respond(200, out);
161
+ return respond(fctx, 200, out);
134
162
  }
135
163
 
136
164
  return null;
@@ -47,12 +47,39 @@ defmodule ProjectName.Feature.Test do
47
47
  nil
48
48
  end
49
49
 
50
- defp respond(status, data, extra \\ nil) do
50
+ # THE MOCK HAS TO AGREE WITH THE MODEL. A point carrying
51
+ # `transform.res: `body.item`` describes an API that answers {"item": {...}}
52
+ # and the response transform unwraps that key on the way back. Returning the
53
+ # bare payload means the transform unwraps a property that is not there and
54
+ # the caller gets nothing. Mirrors the go/ts/lua/php mocks.
55
+ defp envelope(fctx, data) do
56
+ spec = S.getprop(S.getprop(S.getprop(fctx, "point"), "transform"), "res")
57
+
58
+ # Exactly `body.<key>`; a deeper path is not an envelope this mock can
59
+ # synthesise, so it is left alone rather than guessed at.
60
+ case {data, spec} do
61
+ {nil, _} ->
62
+ data
63
+
64
+ {_, s} when is_binary(s) ->
65
+ case Regex.run(~r/^`body\.([^`.]+)`$/, s) do
66
+ [_, inner] -> S.jm([inner, data])
67
+ _ -> data
68
+ end
69
+
70
+ _ ->
71
+ data
72
+ end
73
+ end
74
+
75
+ defp respond(fctx, status, data, extra \\ nil) do
76
+ payload = envelope(fctx, data)
77
+
51
78
  out =
52
79
  S.jm([
53
80
  "status", status,
54
81
  "statusText", "OK",
55
- "json", fn -> data end,
82
+ "json", fn -> payload end,
56
83
  "body", "not-used"
57
84
  ])
58
85
 
@@ -87,10 +114,10 @@ defmodule ProjectName.Feature.Test do
87
114
  ent = S.getelem(found, 0)
88
115
 
89
116
  if ent == nil do
90
- respond(404, nil, S.jm(["statusText", "Not found"]))
117
+ respond(fctx, 404, nil, S.jm(["statusText", "Not found"]))
91
118
  else
92
119
  S.delprop(ent, "$KEY")
93
- respond(200, S.clone(ent))
120
+ respond(fctx, 200, S.clone(ent))
94
121
  end
95
122
 
96
123
  "list" ->
@@ -98,13 +125,13 @@ defmodule ProjectName.Feature.Test do
98
125
  found = S.select(entmap, args)
99
126
 
100
127
  if found == nil do
101
- respond(404, nil, S.jm(["statusText", "Not found"]))
128
+ respond(fctx, 404, nil, S.jm(["statusText", "Not found"]))
102
129
  else
103
130
  if S.islist(found) and S.size(found) > 0 do
104
131
  Enum.each(0..(S.size(found) - 1), fn i -> S.delprop(S.getelem(found, i), "$KEY") end)
105
132
  end
106
133
 
107
- respond(200, S.clone(found))
134
+ respond(fctx, 200, S.clone(found))
108
135
  end
109
136
 
110
137
  "update" ->
@@ -143,14 +170,14 @@ defmodule ProjectName.Feature.Test do
143
170
  end
144
171
 
145
172
  if ent == nil do
146
- respond(404, nil, S.jm(["statusText", "Not found"]))
173
+ respond(fctx, 404, nil, S.jm(["statusText", "Not found"]))
147
174
  else
148
175
  if S.ismap(ent) and reqdata != nil do
149
176
  Enum.each(H.entries(reqdata), fn {k, v} -> S.setprop(ent, k, v) end)
150
177
  end
151
178
 
152
179
  S.delprop(ent, "$KEY")
153
- respond(200, S.clone(ent))
180
+ respond(fctx, 200, S.clone(ent))
154
181
  end
155
182
 
156
183
  "remove" ->
@@ -163,7 +190,7 @@ defmodule ProjectName.Feature.Test do
163
190
  S.delprop(entmap, eid)
164
191
  end
165
192
 
166
- respond(200, nil)
193
+ respond(fctx, 200, nil)
167
194
 
168
195
  "create" ->
169
196
  build_args(f, fctx, op, S.getprop(fctx, "reqdata"))
@@ -175,13 +202,13 @@ defmodule ProjectName.Feature.Test do
175
202
  S.setprop(ent, "id", eid)
176
203
  if is_binary(eid), do: S.setprop(entmap, eid, ent)
177
204
  S.delprop(ent, "$KEY")
178
- respond(200, S.clone(ent))
205
+ respond(fctx, 200, S.clone(ent))
179
206
  else
180
- respond(200, ent)
207
+ respond(fctx, 200, ent)
181
208
  end
182
209
 
183
210
  _ ->
184
- respond(404, nil, S.jm(["statusText", "Unknown operation"]))
211
+ respond(fctx, 404, nil, S.jm(["statusText", "Unknown operation"]))
185
212
  end
186
213
  end
187
214
 
@@ -258,7 +285,7 @@ defmodule ProjectName.Feature.Test do
258
285
  netsleep.(pick_latency(net))
259
286
  status = H.or_(S.getprop(net, "failStatus"), 503)
260
287
 
261
- respond(status, nil,
288
+ respond(fctx, status, nil,
262
289
  S.jm(["statusText", "Simulated Failure", "headers", S.jm([])]))
263
290
 
264
291
  true ->