hecks 3.7.0 → 3.9.1
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.
- checksums.yaml +4 -4
- data/lib/hecks/bluebook/meta_validator/modes.rb +14 -0
- data/lib/hecks/bluebook/meta_validator.rb +4 -2
- data/lib/hecks/corpus/routes.rb +1 -1
- data/lib/hecks/gate/stages.yml +11 -1
- data/lib/hecks/hecks/adapters/site_toolchain/prober.rb +64 -0
- data/lib/hecks/hecks/adapters/site_toolchain.rb +41 -5
- data/lib/hecks/language/bluebook/vocabulary.bluebook +1 -1
- data/lib/hecks/projections/deploy/shared_database_instance/templates/rds.yaml.tmpl +1 -1
- data/lib/hecks/projections/deploy/templates/lambda/template.yaml.tmpl +1 -1
- data/lib/hecks/projections/site/probe.rb +131 -0
- data/lib/hecks/projections/site/site_host/dockerfile.rb +1 -1
- data/lib/hecks/runtime/value/validation.rb +34 -3
- data/lib/hecks/site/bluebook/site.bluebook +31 -0
- data/lib/hecks/site/bluebook/site.ports.hecksagon +6 -0
- data/lib/hecks/version.rb +1 -1
- data/lib/hecks/vocabulary.rb +1 -1
- data/prebuilt/{verdicts-be73f0df9eee0abe2f4aadc968321c4b919d19d424a3ccfe1777a71dabf7d9e7.json → verdicts-91aaf93ce8981bd9a754948f075d95650dc2ec6435015376f5c60992b36a24b1.json} +1 -1
- data/rust/codegen/src/fielded.rs +3 -3
- data/rust/codegen/src/json_codec.rs +2 -2
- data/rust/codegen/src/naming.rs +20 -0
- data/rust/host/Cargo.toml +4 -0
- data/rust/host/HECKS_RELEASE +1 -1
- data/rust/host/src/boot.rs +443 -0
- data/rust/host/src/boundary_fuzz/ir.rs +1 -0
- data/rust/host/src/commerce_ir.rs +459 -0
- data/rust/host/src/extension.rs +100 -0
- data/rust/host/src/ir.rs +0 -448
- data/rust/host/src/journal.rs +1 -1
- data/rust/host/src/lib.rs +40 -0
- data/rust/host/src/main.rs +4 -477
- data/rust/host/src/mint.rs +1 -1
- data/rust/host/src/payments/tests.rs +8 -8
- data/rust/host/src/payments.rs +10 -10
- data/rust/host/src/rate_limit.rs +27 -41
- data/rust/host/src/secrets.rs +15 -0
- data/rust/host/src/web/commerce.rs +121 -0
- data/rust/host/src/web/newsletter.rs +2 -1
- data/rust/host/src/web/newsletter_send.rs +1 -1
- data/rust/host/src/web/registration_receipt.rs +2 -2
- data/rust/host/src/web/registrations.rs +49 -49
- data/rust/host/src/web/resend_webhook.rs +300 -0
- data/rust/host/src/web.rs +43 -47
- metadata +13 -5
- /data/prebuilt/{a0818e2de67364dc1ab22868b6cbf869c62da6f8d9b924c778fa5cce2e76ee94.marshal → 0ee7f83cba59ce7d1c45c65ba166b071779f70143449f584f33ff7989f64eccb.marshal} +0 -0
- /data/prebuilt/{945481eefbdb584649d45f256307053558966a868b9c38dee7ee88f8169610f8.marshal → 6e4a6bcf0aa880e2e6b056e1846b70e5a768090b9dc998bbadeaf5d6bcfd3db8.marshal} +0 -0
data/rust/host/src/ir.rs
CHANGED
|
@@ -132,300 +132,6 @@ pub fn identity_provider(domain_ir: &Value) -> Option<IdentityProvider> {
|
|
|
132
132
|
})
|
|
133
133
|
}
|
|
134
134
|
|
|
135
|
-
/// The chapter that answers the guest newsletter signup, read from
|
|
136
|
-
/// `ir.json`'s own `newsletter` key. `None` when nothing provides it.
|
|
137
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
138
|
-
pub struct NewsletterProvider {
|
|
139
|
-
/// Chapter name, e.g. `Newsletter`.
|
|
140
|
-
pub provider: String,
|
|
141
|
-
/// Qualified subscribe command, e.g. `Newsletter::Subscriber.Subscribe`.
|
|
142
|
-
pub subscribe: String,
|
|
143
|
-
/// Qualified add-name command, e.g. `Newsletter::Subscriber.AddName`.
|
|
144
|
-
pub add_name: String,
|
|
145
|
-
/// Qualified confirm command, e.g. `Newsletter::Subscriber.Confirm`.
|
|
146
|
-
pub confirm: String,
|
|
147
|
-
/// Qualified unsubscribe command, e.g. `Newsletter::Subscriber.Unsubscribe`.
|
|
148
|
-
pub unsubscribe: String,
|
|
149
|
-
/// Qualified subscribing aggregate, e.g. `Newsletter::Subscriber`.
|
|
150
|
-
pub aggregate: String,
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
impl NewsletterProvider {
|
|
154
|
-
/// The prefix every subscriber's key in a `dispatch::read` `instances`
|
|
155
|
-
/// map starts with, e.g. `Newsletter::Subscriber#`.
|
|
156
|
-
pub fn instance_prefix(&self) -> String {
|
|
157
|
-
format!("{}#", self.aggregate)
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/// Whether the domain's own `aggregate`'s `command` declares an argument
|
|
162
|
-
/// named `field` — so a route only sends an argument the command declares.
|
|
163
|
-
pub fn command_declares(domain_ir: &Value, aggregate: &str, command: &str, field: &str) -> bool {
|
|
164
|
-
let named = |value: &Value, name: &str| value.get("name").and_then(|n| n.as_str()) == Some(name);
|
|
165
|
-
let Some(aggregates) = domain_ir.get("aggregates").and_then(|a| a.as_array()) else {
|
|
166
|
-
return false;
|
|
167
|
-
};
|
|
168
|
-
aggregates
|
|
169
|
-
.iter()
|
|
170
|
-
.filter(|a| named(a, aggregate))
|
|
171
|
-
.filter_map(|a| a.get("commands").and_then(|c| c.as_array()))
|
|
172
|
-
.flatten()
|
|
173
|
-
.filter(|c| named(c, command))
|
|
174
|
-
.filter_map(|c| c.get("attributes").and_then(|a| a.as_array()))
|
|
175
|
-
.flatten()
|
|
176
|
-
.any(|attribute| named(attribute, field))
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
pub fn newsletter_provider(domain_ir: &Value) -> Option<NewsletterProvider> {
|
|
180
|
-
let fact = domain_ir.get("newsletter")?;
|
|
181
|
-
Some(NewsletterProvider {
|
|
182
|
-
provider: fact.get("provider")?.as_str()?.to_string(),
|
|
183
|
-
subscribe: fact.get("subscribe")?.as_str()?.to_string(),
|
|
184
|
-
add_name: fact.get("add_name")?.as_str()?.to_string(),
|
|
185
|
-
confirm: fact.get("confirm")?.as_str()?.to_string(),
|
|
186
|
-
unsubscribe: fact.get("unsubscribe")?.as_str()?.to_string(),
|
|
187
|
-
aggregate: fact.get("aggregate")?.as_str()?.to_string(),
|
|
188
|
-
})
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/// The chapter that declares `provides "newsletter_issues"`, read from
|
|
192
|
-
/// `ir.json`'s own `newsletter_issues` key. Omitted when nothing declares it.
|
|
193
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
194
|
-
pub struct NewsletterIssuesProvider {
|
|
195
|
-
/// Qualified send command, e.g. `Newsletter::Issue.Send`.
|
|
196
|
-
pub send_issue: String,
|
|
197
|
-
/// Qualified delivery-record command, e.g. `Newsletter::Delivery.Record`.
|
|
198
|
-
pub record_delivery: String,
|
|
199
|
-
/// Qualified issue aggregate, e.g. `Newsletter::Issue`.
|
|
200
|
-
pub issue_aggregate: String,
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
impl NewsletterIssuesProvider {
|
|
204
|
-
/// The prefix every issue's key in a `dispatch::read` `instances` map
|
|
205
|
-
/// starts with, e.g. `Newsletter::Issue#`.
|
|
206
|
-
pub fn issue_prefix(&self) -> String {
|
|
207
|
-
format!("{}#", self.issue_aggregate)
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
/// The declared issue-sending verbs, or `None` when the IR has no
|
|
212
|
-
/// `newsletter_issues` key.
|
|
213
|
-
pub fn newsletter_issues_provider(domain_ir: &Value) -> Option<NewsletterIssuesProvider> {
|
|
214
|
-
let fact = domain_ir.get("newsletter_issues")?;
|
|
215
|
-
Some(NewsletterIssuesProvider {
|
|
216
|
-
send_issue: fact.get("send_issue")?.as_str()?.to_string(),
|
|
217
|
-
record_delivery: fact.get("record_delivery")?.as_str()?.to_string(),
|
|
218
|
-
issue_aggregate: fact.get("issue_aggregate")?.as_str()?.to_string(),
|
|
219
|
-
})
|
|
220
|
-
}
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
/// The chapter that takes payments, read from `ir.json`'s own `payments`
|
|
224
|
-
/// key. `None` when nothing provides it.
|
|
225
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
226
|
-
pub struct PaymentsProvider {
|
|
227
|
-
/// Chapter name, e.g. `Payments`.
|
|
228
|
-
pub provider: String,
|
|
229
|
-
/// Qualified initiate command, e.g. `Payments::Payment.Initiate`.
|
|
230
|
-
pub initiate: String,
|
|
231
|
-
/// Qualified port operation the processor's success report dispatches,
|
|
232
|
-
/// e.g. `Payments::Payment.PaymentGateway.Succeeded`.
|
|
233
|
-
pub succeeded: String,
|
|
234
|
-
/// Qualified port operation the processor's failure report dispatches,
|
|
235
|
-
/// e.g. `Payments::Payment.PaymentGateway.Failed`.
|
|
236
|
-
pub failed: String,
|
|
237
|
-
/// Qualified paying aggregate, e.g. `Payments::Payment`.
|
|
238
|
-
pub aggregate: String,
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
impl PaymentsProvider {
|
|
242
|
-
/// The prefix every payment's key in a `dispatch::read` `instances` map
|
|
243
|
-
/// starts with, e.g. `Payments::Payment#`.
|
|
244
|
-
pub fn instance_prefix(&self) -> String {
|
|
245
|
-
format!("{}#", self.aggregate)
|
|
246
|
-
}
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
pub fn payments_provider(domain_ir: &Value) -> Option<PaymentsProvider> {
|
|
250
|
-
let fact = domain_ir.get("payments")?;
|
|
251
|
-
Some(PaymentsProvider {
|
|
252
|
-
provider: fact.get("provider")?.as_str()?.to_string(),
|
|
253
|
-
initiate: fact.get("initiate")?.as_str()?.to_string(),
|
|
254
|
-
succeeded: fact.get("succeeded")?.as_str()?.to_string(),
|
|
255
|
-
failed: fact.get("failed")?.as_str()?.to_string(),
|
|
256
|
-
aggregate: fact.get("aggregate")?.as_str()?.to_string(),
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/// The chapter that answers scheduling sessions and taking registrations,
|
|
261
|
-
/// read from `ir.json`'s own `registrations` key. `None` when nothing provides it.
|
|
262
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
263
|
-
pub struct RegistrationsProvider {
|
|
264
|
-
/// Chapter name, e.g. `Studio`.
|
|
265
|
-
pub provider: String,
|
|
266
|
-
/// Qualified schedule command, e.g. `Studio::Event.Schedule`.
|
|
267
|
-
pub schedule: String,
|
|
268
|
-
/// Qualified request command, e.g. `Studio::Registration.Request`.
|
|
269
|
-
pub request: String,
|
|
270
|
-
/// Qualified event aggregate, e.g. `Studio::Event`.
|
|
271
|
-
pub event_aggregate: String,
|
|
272
|
-
/// Qualified registration aggregate, e.g. `Studio::Registration`.
|
|
273
|
-
pub registration_aggregate: String,
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
impl RegistrationsProvider {
|
|
277
|
-
/// The conventional names for a domain that declares no
|
|
278
|
-
/// `provides "registrations"` — `Event`/`Registration` under the domain itself.
|
|
279
|
-
pub fn conventional(domain: &str) -> Self {
|
|
280
|
-
Self {
|
|
281
|
-
provider: domain.to_string(),
|
|
282
|
-
schedule: format!("{domain}::Event.Schedule"),
|
|
283
|
-
request: format!("{domain}::Registration.Request"),
|
|
284
|
-
event_aggregate: format!("{domain}::Event"),
|
|
285
|
-
registration_aggregate: format!("{domain}::Registration"),
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/// The prefix every event's key in a `dispatch::read` `instances` map
|
|
290
|
-
/// starts with, e.g. `Studio::Event#`.
|
|
291
|
-
pub fn event_prefix(&self) -> String {
|
|
292
|
-
format!("{}#", self.event_aggregate)
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/// The prefix every registration's key starts with, e.g.
|
|
296
|
-
/// `Studio::Registration#`.
|
|
297
|
-
pub fn registration_prefix(&self) -> String {
|
|
298
|
-
format!("{}#", self.registration_aggregate)
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
/// The request command's own aggregate and command name without the
|
|
302
|
-
/// chapter qualifier, e.g. `("Registration", "Request")`, as `command_declares` expects.
|
|
303
|
-
pub fn request_target(&self) -> Option<(&str, &str)> {
|
|
304
|
-
self.request.rsplit("::").next()?.split_once('.')
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
pub fn registrations_provider(domain_ir: &Value) -> Option<RegistrationsProvider> {
|
|
309
|
-
let fact = domain_ir.get("registrations")?;
|
|
310
|
-
Some(RegistrationsProvider {
|
|
311
|
-
provider: fact.get("provider")?.as_str()?.to_string(),
|
|
312
|
-
schedule: fact.get("schedule")?.as_str()?.to_string(),
|
|
313
|
-
request: fact.get("request")?.as_str()?.to_string(),
|
|
314
|
-
event_aggregate: fact.get("event_aggregate")?.as_str()?.to_string(),
|
|
315
|
-
registration_aggregate: fact.get("registration_aggregate")?.as_str()?.to_string(),
|
|
316
|
-
})
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
/// The registrations binding of the IR this host loaded, or the
|
|
320
|
-
/// conventional names for `domain` when the IR declares none.
|
|
321
|
-
pub fn registrations_binding(domain: &str) -> RegistrationsProvider {
|
|
322
|
-
ir().and_then(registrations_provider).unwrap_or_else(|| RegistrationsProvider::conventional(domain))
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
/// One command of the payment-processor connection, by the role it plays —
|
|
326
|
-
/// resolved through `PaymentConnectionProvider::verb`.
|
|
327
|
-
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
|
328
|
-
pub enum ConnectionVerb {
|
|
329
|
-
Connect,
|
|
330
|
-
Reconnect,
|
|
331
|
-
Disconnect,
|
|
332
|
-
Suspend,
|
|
333
|
-
Resume,
|
|
334
|
-
Enable,
|
|
335
|
-
Disable,
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
/// The chapter that owns the business's payment-processor connection, read
|
|
339
|
-
/// from `ir.json`'s own `payment_connection` key. `None` when nothing provides it.
|
|
340
|
-
#[derive(Debug, Clone, PartialEq)]
|
|
341
|
-
pub struct PaymentConnectionProvider {
|
|
342
|
-
/// Chapter name, e.g. `Studio`.
|
|
343
|
-
pub provider: String,
|
|
344
|
-
pub connect: String,
|
|
345
|
-
pub reconnect: String,
|
|
346
|
-
pub disconnect: String,
|
|
347
|
-
pub suspend: String,
|
|
348
|
-
pub resume: String,
|
|
349
|
-
pub enable: String,
|
|
350
|
-
pub disable: String,
|
|
351
|
-
/// Qualified connection aggregate, e.g. `Studio::PaymentConnection`.
|
|
352
|
-
pub aggregate: String,
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
impl PaymentConnectionProvider {
|
|
356
|
-
/// The conventional names for a domain that declares no
|
|
357
|
-
/// `provides "payment_connection"` — `PaymentConnection` under the domain itself.
|
|
358
|
-
pub fn conventional(domain: &str) -> Self {
|
|
359
|
-
let qualified = |command: &str| format!("{domain}::PaymentConnection.{command}");
|
|
360
|
-
Self {
|
|
361
|
-
provider: domain.to_string(),
|
|
362
|
-
connect: qualified("Connect"),
|
|
363
|
-
reconnect: qualified("Reconnect"),
|
|
364
|
-
disconnect: qualified("Disconnect"),
|
|
365
|
-
suspend: qualified("Suspend"),
|
|
366
|
-
resume: qualified("Resume"),
|
|
367
|
-
enable: qualified("EnablePayments"),
|
|
368
|
-
disable: qualified("DisablePayments"),
|
|
369
|
-
aggregate: format!("{domain}::PaymentConnection"),
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
/// The prefix every connection's key in a `dispatch::read` `instances`
|
|
374
|
-
/// map starts with, e.g. `Studio::PaymentConnection#`.
|
|
375
|
-
pub fn instance_prefix(&self) -> String {
|
|
376
|
-
format!("{}#", self.aggregate)
|
|
377
|
-
}
|
|
378
|
-
|
|
379
|
-
/// The qualified command that plays `which`.
|
|
380
|
-
pub fn verb(&self, which: ConnectionVerb) -> &str {
|
|
381
|
-
match which {
|
|
382
|
-
ConnectionVerb::Connect => &self.connect,
|
|
383
|
-
ConnectionVerb::Reconnect => &self.reconnect,
|
|
384
|
-
ConnectionVerb::Disconnect => &self.disconnect,
|
|
385
|
-
ConnectionVerb::Suspend => &self.suspend,
|
|
386
|
-
ConnectionVerb::Resume => &self.resume,
|
|
387
|
-
ConnectionVerb::Enable => &self.enable,
|
|
388
|
-
ConnectionVerb::Disable => &self.disable,
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
|
|
393
|
-
pub fn payment_connection_provider(domain_ir: &Value) -> Option<PaymentConnectionProvider> {
|
|
394
|
-
let fact = domain_ir.get("payment_connection")?;
|
|
395
|
-
let text = |key: &str| Some(fact.get(key)?.as_str()?.to_string());
|
|
396
|
-
Some(PaymentConnectionProvider {
|
|
397
|
-
provider: text("provider")?,
|
|
398
|
-
connect: text("connect")?,
|
|
399
|
-
reconnect: text("reconnect")?,
|
|
400
|
-
disconnect: text("disconnect")?,
|
|
401
|
-
suspend: text("suspend")?,
|
|
402
|
-
resume: text("resume")?,
|
|
403
|
-
enable: text("enable")?,
|
|
404
|
-
disable: text("disable")?,
|
|
405
|
-
aggregate: text("aggregate")?,
|
|
406
|
-
})
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
/// The payment-connection binding of the IR this host loaded, or the
|
|
410
|
-
/// conventional names for `domain` when the IR declares none.
|
|
411
|
-
pub fn payment_connection_binding(domain: &str) -> PaymentConnectionProvider {
|
|
412
|
-
ir().and_then(payment_connection_provider).unwrap_or_else(|| PaymentConnectionProvider::conventional(domain))
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
/// The checkout fixture's own payments binding, read from its committed
|
|
416
|
-
/// `ir.json` — what the checkout routes' tests dispatch against.
|
|
417
|
-
#[cfg(test)]
|
|
418
|
-
pub fn fixture_ir() -> Value {
|
|
419
|
-
let path = std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("../src/generated/checkout_fixture/ir.json");
|
|
420
|
-
serde_json::from_str(&std::fs::read_to_string(path).expect("checkout_fixture ir.json")).expect("valid json")
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
/// The checkout fixture's own payments binding.
|
|
424
|
-
#[cfg(test)]
|
|
425
|
-
pub fn fixture_payments() -> PaymentsProvider {
|
|
426
|
-
payments_provider(&fixture_ir()).expect("the checkout fixture provides payments")
|
|
427
|
-
}
|
|
428
|
-
|
|
429
135
|
/// Refuses at boot rather than silently building a second, disjoint history
|
|
430
136
|
/// nothing but this runtime reads while the real state lives elsewhere.
|
|
431
137
|
pub fn refuse_unsupported_persistence_adapters(domain_ir: &Value) -> Result<(), String> {
|
|
@@ -570,160 +276,6 @@ mod tests {
|
|
|
570
276
|
assert!(identity_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
|
|
571
277
|
}
|
|
572
278
|
|
|
573
|
-
#[test]
|
|
574
|
-
fn newsletter_provider_reads_the_declared_capability() {
|
|
575
|
-
let ir = serde_json::json!({
|
|
576
|
-
"name": "Studio",
|
|
577
|
-
"newsletter": {
|
|
578
|
-
"provider": "Newsletter",
|
|
579
|
-
"subscribe": "Newsletter::Subscriber.Subscribe",
|
|
580
|
-
"add_name": "Newsletter::Subscriber.AddName",
|
|
581
|
-
"confirm": "Newsletter::Subscriber.Confirm",
|
|
582
|
-
"unsubscribe": "Newsletter::Subscriber.Unsubscribe",
|
|
583
|
-
"aggregate": "Newsletter::Subscriber"
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
let provider = newsletter_provider(&ir).expect("should find newsletter");
|
|
587
|
-
assert_eq!(provider.provider, "Newsletter");
|
|
588
|
-
assert_eq!(provider.subscribe, "Newsletter::Subscriber.Subscribe");
|
|
589
|
-
assert_eq!(provider.add_name, "Newsletter::Subscriber.AddName");
|
|
590
|
-
assert_eq!(provider.confirm, "Newsletter::Subscriber.Confirm");
|
|
591
|
-
assert_eq!(provider.unsubscribe, "Newsletter::Subscriber.Unsubscribe");
|
|
592
|
-
assert_eq!(provider.instance_prefix(), "Newsletter::Subscriber#");
|
|
593
|
-
assert!(newsletter_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
#[test]
|
|
597
|
-
fn newsletter_issues_provider_reads_the_declared_capability() {
|
|
598
|
-
let ir = serde_json::json!({
|
|
599
|
-
"name": "Studio",
|
|
600
|
-
"newsletter_issues": {
|
|
601
|
-
"provider": "Newsletter",
|
|
602
|
-
"send_issue": "Newsletter::Issue.Send",
|
|
603
|
-
"record_delivery": "Newsletter::Delivery.Record",
|
|
604
|
-
"issue_aggregate": "Newsletter::Issue",
|
|
605
|
-
"delivery_aggregate": "Newsletter::Delivery"
|
|
606
|
-
}
|
|
607
|
-
});
|
|
608
|
-
let provider = newsletter_issues_provider(&ir).expect("should find newsletter_issues");
|
|
609
|
-
assert_eq!(provider.send_issue, "Newsletter::Issue.Send");
|
|
610
|
-
assert_eq!(provider.record_delivery, "Newsletter::Delivery.Record");
|
|
611
|
-
assert_eq!(provider.issue_prefix(), "Newsletter::Issue#");
|
|
612
|
-
assert!(newsletter_issues_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
|
|
613
|
-
}
|
|
614
|
-
|
|
615
|
-
#[test]
|
|
616
|
-
fn payments_provider_reads_the_declared_capability() {
|
|
617
|
-
let ir = serde_json::json!({
|
|
618
|
-
"name": "Studio",
|
|
619
|
-
"payments": {
|
|
620
|
-
"provider": "Payments",
|
|
621
|
-
"initiate": "Payments::Payment.Initiate",
|
|
622
|
-
"succeeded": "Payments::Payment.PaymentGateway.Succeeded",
|
|
623
|
-
"failed": "Payments::Payment.PaymentGateway.Failed",
|
|
624
|
-
"aggregate": "Payments::Payment"
|
|
625
|
-
}
|
|
626
|
-
});
|
|
627
|
-
let provider = payments_provider(&ir).expect("should find payments");
|
|
628
|
-
assert_eq!(provider.provider, "Payments");
|
|
629
|
-
assert_eq!(provider.initiate, "Payments::Payment.Initiate");
|
|
630
|
-
assert_eq!(provider.succeeded, "Payments::Payment.PaymentGateway.Succeeded");
|
|
631
|
-
assert_eq!(provider.failed, "Payments::Payment.PaymentGateway.Failed");
|
|
632
|
-
assert_eq!(provider.instance_prefix(), "Payments::Payment#");
|
|
633
|
-
assert!(payments_provider(&serde_json::json!({ "name": "Pizzas" })).is_none());
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
#[test]
|
|
637
|
-
fn registrations_provider_reads_the_declared_capability() {
|
|
638
|
-
let provider = registrations_provider(&fixture_ir()).expect("the checkout fixture provides registrations");
|
|
639
|
-
assert_eq!(provider.schedule, "CheckoutFixture::Event.Schedule");
|
|
640
|
-
assert_eq!(provider.request, "CheckoutFixture::Registration.Request");
|
|
641
|
-
assert_eq!(provider.event_prefix(), "CheckoutFixture::Event#");
|
|
642
|
-
assert_eq!(provider.registration_prefix(), "CheckoutFixture::Registration#");
|
|
643
|
-
assert_eq!(provider.request_target(), Some(("Registration", "Request")));
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
#[test]
|
|
647
|
-
fn the_conventional_registrations_names_match_what_the_fixture_declares() {
|
|
648
|
-
assert_eq!(registrations_provider(&fixture_ir()), Some(RegistrationsProvider::conventional("CheckoutFixture")));
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
#[test]
|
|
652
|
-
fn a_declared_registrations_binding_names_whatever_the_chapter_calls_things() {
|
|
653
|
-
let ir = serde_json::json!({"registrations": {
|
|
654
|
-
"provider": "Bookings",
|
|
655
|
-
"schedule": "Bookings::Session.Plan",
|
|
656
|
-
"request": "Bookings::Seat.Claim",
|
|
657
|
-
"event_aggregate": "Bookings::Session",
|
|
658
|
-
"registration_aggregate": "Bookings::Seat"
|
|
659
|
-
}});
|
|
660
|
-
let provider = registrations_provider(&ir).expect("declared");
|
|
661
|
-
assert_eq!(provider.event_prefix(), "Bookings::Session#");
|
|
662
|
-
assert_eq!(provider.registration_prefix(), "Bookings::Seat#");
|
|
663
|
-
assert_eq!(provider.request_target(), Some(("Seat", "Claim")));
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
#[test]
|
|
667
|
-
fn registrations_provider_is_none_without_the_key() {
|
|
668
|
-
assert_eq!(registrations_provider(&serde_json::json!({"name": "Pizzas"})), None);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
#[test]
|
|
672
|
-
fn registrations_binding_falls_back_to_the_conventional_names_when_no_ir_is_loaded() {
|
|
673
|
-
assert_eq!(registrations_binding("Shop"), RegistrationsProvider::conventional("Shop"));
|
|
674
|
-
}
|
|
675
|
-
|
|
676
|
-
#[test]
|
|
677
|
-
fn payment_connection_provider_reads_the_declared_capability() {
|
|
678
|
-
let provider = payment_connection_provider(&fixture_ir()).expect("the checkout fixture provides payment_connection");
|
|
679
|
-
assert_eq!(provider.verb(ConnectionVerb::Connect), "CheckoutFixture::PaymentConnection.Connect");
|
|
680
|
-
assert_eq!(provider.verb(ConnectionVerb::Enable), "CheckoutFixture::PaymentConnection.EnablePayments");
|
|
681
|
-
assert_eq!(provider.verb(ConnectionVerb::Disable), "CheckoutFixture::PaymentConnection.DisablePayments");
|
|
682
|
-
assert_eq!(provider.instance_prefix(), "CheckoutFixture::PaymentConnection#");
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
#[test]
|
|
686
|
-
fn the_conventional_payment_connection_names_match_what_the_fixture_declares() {
|
|
687
|
-
assert_eq!(payment_connection_provider(&fixture_ir()), Some(PaymentConnectionProvider::conventional("CheckoutFixture")));
|
|
688
|
-
}
|
|
689
|
-
|
|
690
|
-
#[test]
|
|
691
|
-
fn a_declared_payment_connection_binding_names_whatever_the_chapter_calls_things() {
|
|
692
|
-
let ir = serde_json::json!({"payment_connection": {
|
|
693
|
-
"provider": "Billing",
|
|
694
|
-
"connect": "Billing::Link.Open", "reconnect": "Billing::Link.Reopen",
|
|
695
|
-
"disconnect": "Billing::Link.Close", "suspend": "Billing::Link.Pause",
|
|
696
|
-
"resume": "Billing::Link.Unpause", "enable": "Billing::Link.TurnOn",
|
|
697
|
-
"disable": "Billing::Link.TurnOff", "aggregate": "Billing::Link"
|
|
698
|
-
}});
|
|
699
|
-
let provider = payment_connection_provider(&ir).expect("declared");
|
|
700
|
-
assert_eq!(provider.verb(ConnectionVerb::Suspend), "Billing::Link.Pause");
|
|
701
|
-
assert_eq!(provider.verb(ConnectionVerb::Resume), "Billing::Link.Unpause");
|
|
702
|
-
assert_eq!(provider.instance_prefix(), "Billing::Link#");
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
#[test]
|
|
706
|
-
fn payment_connection_binding_falls_back_to_the_conventional_names_when_no_ir_is_loaded() {
|
|
707
|
-
assert_eq!(payment_connection_binding("Shop"), PaymentConnectionProvider::conventional("Shop"));
|
|
708
|
-
}
|
|
709
|
-
|
|
710
|
-
#[test]
|
|
711
|
-
fn command_declares_finds_an_argument_on_the_named_command_only() {
|
|
712
|
-
let ir = serde_json::json!({
|
|
713
|
-
"aggregates": [
|
|
714
|
-
{"name": "Registration", "commands": [
|
|
715
|
-
{"name": "Request", "attributes": [{"name": "email"}, {"name": "news_signup"}]},
|
|
716
|
-
{"name": "Cancel", "attributes": [{"name": "reason"}]}
|
|
717
|
-
]},
|
|
718
|
-
{"name": "Event", "commands": [{"name": "Request", "attributes": [{"name": "slug"}]}]}
|
|
719
|
-
]
|
|
720
|
-
});
|
|
721
|
-
assert!(command_declares(&ir, "Registration", "Request", "news_signup"));
|
|
722
|
-
assert!(!command_declares(&ir, "Registration", "Cancel", "news_signup"));
|
|
723
|
-
assert!(!command_declares(&ir, "Event", "Request", "news_signup"));
|
|
724
|
-
assert!(!command_declares(&ir, "Registration", "Request", "phone"));
|
|
725
|
-
assert!(!command_declares(&serde_json::json!({"name": "Pizzas"}), "Registration", "Request", "news_signup"));
|
|
726
|
-
}
|
|
727
279
|
|
|
728
280
|
#[test]
|
|
729
281
|
fn refuse_unsupported_persistence_adapters_passes_a_domain_with_no_persistence_key() {
|
data/rust/host/src/journal.rs
CHANGED
|
@@ -334,7 +334,7 @@ fn storage_name(qualified_aggregate: &str) -> String {
|
|
|
334
334
|
|
|
335
335
|
/// Quotes a Postgres identifier `PG::Connection.quote_ident`-style —
|
|
336
336
|
/// needed since table names can't be bound as query parameters.
|
|
337
|
-
pub
|
|
337
|
+
pub fn quote_ident(name: &str) -> String {
|
|
338
338
|
format!("\"{}\"", name.replace('"', "\"\""))
|
|
339
339
|
}
|
|
340
340
|
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
//! The host as a library: the modules the `bootstrap` binary boots, plus the harness binaries' shared code.
|
|
2
|
+
//! Commerce (payments, checkout, newsletter, registrations, mail) plugs in through [`extension::HostExtension`].
|
|
3
|
+
|
|
4
|
+
pub mod api;
|
|
5
|
+
pub mod approval;
|
|
6
|
+
pub mod auth;
|
|
7
|
+
pub mod boot;
|
|
8
|
+
pub mod checkout;
|
|
9
|
+
pub mod commerce_ir;
|
|
10
|
+
pub mod dispatch;
|
|
11
|
+
pub mod expr_json;
|
|
12
|
+
pub mod extension;
|
|
13
|
+
pub mod field_hints;
|
|
14
|
+
#[cfg(test)]
|
|
15
|
+
pub mod fuzz_support;
|
|
16
|
+
#[cfg(test)]
|
|
17
|
+
#[path = "boundary_fuzz/expr_json.rs"]
|
|
18
|
+
pub mod expr_json_fuzz;
|
|
19
|
+
pub mod ir;
|
|
20
|
+
pub mod journal;
|
|
21
|
+
pub mod lambda_client;
|
|
22
|
+
pub mod log;
|
|
23
|
+
pub mod mint;
|
|
24
|
+
pub mod needs;
|
|
25
|
+
pub mod payments;
|
|
26
|
+
pub mod presentation;
|
|
27
|
+
pub mod presentation_write;
|
|
28
|
+
pub mod query_step;
|
|
29
|
+
pub mod rate_limit;
|
|
30
|
+
pub mod reference_transform;
|
|
31
|
+
pub mod reference_validate;
|
|
32
|
+
pub mod resend;
|
|
33
|
+
pub mod secrets;
|
|
34
|
+
pub mod server;
|
|
35
|
+
pub mod storage_shape;
|
|
36
|
+
#[cfg(test)]
|
|
37
|
+
pub mod test_pg;
|
|
38
|
+
pub mod ui_schema;
|
|
39
|
+
pub mod wasm_runner;
|
|
40
|
+
pub mod web;
|