1use std::collections::BTreeMap;
4
5use serde_json::{Map, Value};
6
7use crate::DbBackendConfig;
8use crate::operations::{
9 AppSchema, DatabaseError, DatabaseOperations, Mutation, Predicate, Query, SortKey, Transaction,
10 WriteValue,
11};
12
13fn declared_text<'a>(declaration: &'a Value, key: &str) -> Result<&'a str, DatabaseError> {
14 declaration
15 .get(key)
16 .and_then(Value::as_str)
17 .filter(|value| !value.is_empty() && value.len() <= 128)
18 .ok_or(DatabaseError::InvalidContract)
19}
20
21fn declared_fields(
22 schema: &AppSchema,
23 entity: &str,
24 names: &[&str],
25) -> Result<Vec<String>, DatabaseError> {
26 let declared = schema
27 .entities
28 .iter()
29 .find(|candidate| candidate.name == entity)
30 .ok_or(DatabaseError::InvalidContract)?;
31 let mut fields = Vec::new();
32 for name in names {
33 if !declared.fields.iter().any(|field| field.name == *name) {
34 return Err(DatabaseError::InvalidContract);
35 }
36 if !fields.iter().any(|field| field == name) {
37 fields.push((*name).to_owned());
38 }
39 }
40 Ok(fields)
41}
42
43fn all_fields(schema: &AppSchema, entity: &str) -> Result<Vec<String>, DatabaseError> {
45 Ok(schema
46 .entities
47 .iter()
48 .find(|definition| definition.name == entity)
49 .ok_or(DatabaseError::InvalidContract)?
50 .fields
51 .iter()
52 .map(|field| field.name.clone())
53 .collect())
54}
55
56fn one_row(
57 adapter: &dyn DatabaseOperations,
58 config: &DbBackendConfig,
59 schema: &AppSchema,
60 entity: &str,
61 fields: Vec<String>,
62 filter: Option<Predicate>,
63) -> Result<Option<Map<String, Value>>, DatabaseError> {
64 let mut rows = adapter.query(
65 config,
66 schema,
67 &Query {
68 entity: entity.to_owned(),
69 fields,
70 filter,
71 order: Vec::new(),
72 limit: 2,
73 offset: 0,
74 },
75 )?;
76 if rows.len() > 1 {
77 return Err(DatabaseError::Conflict);
78 }
79 Ok(rows.pop())
80}
81
82pub fn payment_render_state(
87 adapter: &dyn DatabaseOperations,
88 config: &DbBackendConfig,
89 schema: &AppSchema,
90 declaration: &Value,
91 owner_route_value: Option<&str>,
92 provider_id: &str,
93) -> Result<Value, DatabaseError> {
94 payment_state(
95 adapter,
96 config,
97 schema,
98 declaration,
99 owner_route_value,
100 provider_id,
101 None,
102 )
103}
104
105pub fn payment_lifecycle_state(
107 adapter: &dyn DatabaseOperations,
108 config: &DbBackendConfig,
109 schema: &AppSchema,
110 declaration: &Value,
111 owner_route_value: &str,
112 provider_id: &str,
113 actor_id: i64,
114) -> Result<Value, DatabaseError> {
115 if actor_id <= 0 {
116 return Err(DatabaseError::InvalidInput);
117 }
118 payment_state(
119 adapter,
120 config,
121 schema,
122 declaration,
123 Some(owner_route_value),
124 provider_id,
125 Some(actor_id),
126 )
127}
128
129fn payment_state(
130 adapter: &dyn DatabaseOperations,
131 config: &DbBackendConfig,
132 schema: &AppSchema,
133 declaration: &Value,
134 owner_route_value: Option<&str>,
135 provider_id: &str,
136 actor_id: Option<i64>,
137) -> Result<Value, DatabaseError> {
138 if provider_id.is_empty() || provider_id.len() > 64 {
139 return Err(DatabaseError::InvalidInput);
140 }
141 let owner_entity = declared_text(declaration, "owner_entity")?;
142 let owner_section = declared_text(declaration, "owner_state_section")?;
143 let owner_route_field = declared_text(declaration, "owner_route_field")?;
144 let owner_id_field = declared_text(declaration, "owner_id_field")?;
145 let owner_type = declared_text(declaration, "owner_resource_type")?;
146 let tenant_entity = declared_text(declaration, "tenant_entity")?;
147 let tenant_section = declared_text(declaration, "tenant_state_section")?;
148 let tenant_owner_id_field = declared_text(declaration, "tenant_owner_id_field")?;
149 let tenant_id_field = declared_text(declaration, "tenant_id_field")?;
150 let mut owner_names = vec![owner_route_field, owner_id_field];
151 if let Some(field) = declaration
152 .get("owner_user_id_field")
153 .and_then(Value::as_str)
154 {
155 owner_names.push(field);
156 }
157 if tenant_entity == owner_entity {
158 owner_names.push(tenant_owner_id_field);
159 owner_names.push(tenant_id_field);
160 if let Some(field) = declaration
161 .get("tenant_owner_user_id_field")
162 .and_then(Value::as_str)
163 {
164 owner_names.push(field);
165 }
166 }
167 let owner_fields = declared_fields(schema, owner_entity, &owner_names)?;
168 let owner_filter = owner_route_value
169 .map(|value| {
170 if value.is_empty() || value.len() > 128 {
171 return Err(DatabaseError::InvalidInput);
172 }
173 Ok(Predicate::Equal(
174 owner_route_field.to_owned(),
175 Value::String(value.to_owned()),
176 ))
177 })
178 .transpose()?;
179 let owner_result = one_row(
180 adapter,
181 config,
182 schema,
183 owner_entity,
184 owner_fields,
185 owner_filter,
186 );
187 if owner_route_value.is_none() && matches!(owner_result, Err(DatabaseError::Conflict)) {
188 return Ok(Value::Object(Map::new()));
189 }
190 let Some(owner) = owner_result? else {
191 return Ok(Value::Object(Map::new()));
192 };
193 let owner_id = owner
194 .get(owner_id_field)
195 .and_then(Value::as_i64)
196 .filter(|id| *id > 0)
197 .ok_or(DatabaseError::StorageFailure)?;
198 let tenant = if tenant_entity == owner_entity && tenant_section == owner_section {
199 owner.clone()
200 } else {
201 let mut tenant_names = vec![tenant_id_field, tenant_owner_id_field];
202 if let Some(field) = declaration
203 .get("tenant_owner_user_id_field")
204 .and_then(Value::as_str)
205 {
206 tenant_names.push(field);
207 }
208 let tenant_fields = declared_fields(schema, tenant_entity, &tenant_names)?;
209 let Some(row) = one_row(
210 adapter,
211 config,
212 schema,
213 tenant_entity,
214 tenant_fields,
215 Some(Predicate::Equal(
216 tenant_owner_id_field.to_owned(),
217 Value::from(owner_id),
218 )),
219 )?
220 else {
221 return Ok(Value::Object(Map::new()));
222 };
223 row
224 };
225 let tenant_id = tenant
226 .get(tenant_id_field)
227 .and_then(Value::as_i64)
228 .filter(|id| *id > 0)
229 .ok_or(DatabaseError::StorageFailure)?;
230 let scope = Predicate::All(vec![
231 Predicate::Equal("app_id".to_owned(), Value::String(schema.app_id.clone())),
232 Predicate::Equal("tenant_id".to_owned(), Value::from(tenant_id)),
233 Predicate::Equal(
234 "owner_resource_type".to_owned(),
235 Value::String(owner_type.to_owned()),
236 ),
237 Predicate::Equal("owner_resource_id".to_owned(), Value::from(owner_id)),
238 Predicate::Equal(
239 "provider_id".to_owned(),
240 Value::String(provider_id.to_owned()),
241 ),
242 ]);
243 let account_fields = if actor_id.is_some() {
244 all_fields(schema, "payment_provider_account")?
245 } else {
246 declared_fields(
247 schema,
248 "payment_provider_account",
249 &[
250 "app_id",
251 "id",
252 "tenant_id",
253 "owner_resource_type",
254 "owner_resource_id",
255 "provider_id",
256 "checkout_mode",
257 "connection_state",
258 "capability_state",
259 "onboarding_state",
260 "provider_account_id",
261 "provider_account_label",
262 "status_reason_code",
263 "status_reason_detail",
264 "charges_enabled",
265 "payouts_enabled",
266 "details_submitted",
267 ],
268 )?
269 };
270 let accounts = adapter.query(
271 config,
272 schema,
273 &Query {
274 entity: "payment_provider_account".to_owned(),
275 fields: account_fields,
276 filter: Some(scope.clone()),
277 order: Vec::new(),
278 limit: 2,
279 offset: 0,
280 },
281 )?;
282 if accounts.len() > 1 {
283 return Err(DatabaseError::Conflict);
284 }
285 let secret_fields = if actor_id.is_some() {
286 all_fields(schema, "payment_provider_secret")?
287 } else {
288 declared_fields(
289 schema,
290 "payment_provider_secret",
291 &[
292 "app_id",
293 "tenant_id",
294 "owner_resource_type",
295 "owner_resource_id",
296 "provider_id",
297 "secret_kind",
298 "ciphertext",
299 "revoked_at",
300 ],
301 )?
302 };
303 let mut secrets = adapter.query(
304 config,
305 schema,
306 &Query {
307 entity: "payment_provider_secret".to_owned(),
308 fields: secret_fields,
309 filter: Some(scope.clone()),
310 order: Vec::new(),
311 limit: 17,
312 offset: 0,
313 },
314 )?;
315 if secrets.len() > 16 {
316 return Err(DatabaseError::CapacityExceeded);
317 }
318 for row in secrets.iter_mut().filter(|_| actor_id.is_none()) {
319 let present = row
320 .get("ciphertext")
321 .and_then(Value::as_str)
322 .is_some_and(|text| !text.is_empty());
323 row.insert(
324 "ciphertext".to_owned(),
325 Value::String(if present { "present" } else { "" }.to_owned()),
326 );
327 }
328 let mut root = Map::new();
329 root.insert(
330 owner_section.to_owned(),
331 Value::Array(vec![Value::Object(owner)]),
332 );
333 if tenant_section != owner_section {
334 root.insert(
335 tenant_section.to_owned(),
336 Value::Array(vec![Value::Object(tenant)]),
337 );
338 }
339 root.insert(
340 "payment_provider_accounts".to_owned(),
341 Value::Array(accounts.into_iter().map(Value::Object).collect()),
342 );
343 root.insert(
344 "payment_provider_secrets".to_owned(),
345 Value::Array(secrets.into_iter().map(Value::Object).collect()),
346 );
347 if let Some(actor_id) = actor_id {
348 root.insert(
349 "_payment_scope".to_owned(),
350 serde_json::json!({
351 "app_id": schema.app_id, "tenant_id": tenant_id,
352 "owner_resource_type": owner_type, "owner_resource_id": owner_id,
353 "provider_id": provider_id, "actor_user_id": actor_id,
354 }),
355 );
356 if let Some(entity) = declaration.get("membership_entity").and_then(Value::as_str) {
357 let section = declared_text(declaration, "membership_state_section")?;
358 let user_field = declared_text(declaration, "membership_user_id_field")?;
359 let tenant_field = declared_text(declaration, "membership_tenant_id_field")?;
360 let status_field = declared_text(declaration, "membership_status_field")?;
361 let mut membership_fields = vec![user_field, tenant_field, status_field];
362 for key in ["membership_role_field", "membership_revoked_field"] {
363 if let Some(field) = declaration.get(key).and_then(Value::as_str) {
364 membership_fields.push(field);
365 }
366 }
367 let rows = adapter.query(
368 config,
369 schema,
370 &Query {
371 entity: entity.to_owned(),
372 fields: declared_fields(schema, entity, &membership_fields)?,
373 filter: Some(Predicate::All(vec![
374 Predicate::Equal(user_field.to_owned(), Value::from(actor_id)),
375 Predicate::Equal(tenant_field.to_owned(), Value::from(tenant_id)),
376 ])),
377 order: Vec::new(),
378 limit: 2,
379 offset: 0,
380 },
381 )?;
382 if rows.len() > 1 {
383 return Err(DatabaseError::Conflict);
384 }
385 root.insert(
386 section.to_owned(),
387 Value::Array(rows.into_iter().map(Value::Object).collect()),
388 );
389 }
390 let epoch = std::time::SystemTime::now()
391 .duration_since(std::time::UNIX_EPOCH)
392 .map_err(|_| DatabaseError::StorageFailure)?
393 .as_secs();
394 let now = i64::try_from(epoch)
395 .ok()
396 .and_then(|epoch| chrono::DateTime::<chrono::Utc>::from_timestamp(epoch, 0))
397 .ok_or(DatabaseError::StorageFailure)?
398 .to_rfc3339();
399 let nonces = adapter.query(
400 config,
401 schema,
402 &Query {
403 entity: "payment_provider_callback_nonce".to_owned(),
404 fields: all_fields(schema, "payment_provider_callback_nonce")?,
405 filter: Some(Predicate::All(vec![
406 scope,
407 Predicate::IsNull("claimed_at".to_owned()),
408 Predicate::GreaterThan("expires_at".to_owned(), Value::String(now)),
409 ])),
410 order: Vec::new(),
411 limit: 257,
412 offset: 0,
413 },
414 )?;
415 if nonces.len() > 256 {
416 return Err(DatabaseError::CapacityExceeded);
417 }
418 root.insert(
419 "payment_provider_callback_nonces".to_owned(),
420 Value::Array(nonces.into_iter().map(Value::Object).collect()),
421 );
422 root.insert(
423 "payment_provider_audit_events".to_owned(),
424 Value::Array(Vec::new()),
425 );
426 for (section, entity) in PAYMENT_SECTIONS {
430 let latest = adapter.query(
431 config,
432 schema,
433 &Query {
434 entity: (*entity).to_owned(),
435 fields: vec!["id".to_owned()],
436 filter: None,
437 order: vec![SortKey {
438 field: "id".to_owned(),
439 descending: true,
440 }],
441 limit: 1,
442 offset: 0,
443 },
444 )?;
445 let rows = root
446 .get_mut(*section)
447 .and_then(Value::as_array_mut)
448 .ok_or(DatabaseError::InvalidInput)?;
449 for row in latest {
450 if !rows
451 .iter()
452 .any(|existing| existing.get("id") == row.get("id"))
453 {
454 rows.push(Value::Object(row));
455 }
456 }
457 }
458 }
459 Ok(Value::Object(root))
460}
461
462const PAYMENT_SECTIONS: &[(&str, &str)] = &[
463 ("payment_provider_secrets", "payment_provider_secret"),
464 ("payment_provider_accounts", "payment_provider_account"),
465 (
466 "payment_provider_callback_nonces",
467 "payment_provider_callback_nonce",
468 ),
469 (
470 "payment_provider_audit_events",
471 "payment_provider_audit_event",
472 ),
473];
474
475fn identity_reference<'a>(entity: &str, field: &'a crate::operations::Field) -> Option<&'a str> {
476 if entity == "payment_provider_account" && field.name == "secret_reference_id" {
477 Some("payment_provider_secret")
478 } else {
479 field
480 .references
481 .as_ref()
482 .filter(|(_, target)| target == "id")
483 .map(|(target, _)| target.as_str())
484 }
485}
486
487pub fn apply_payment_lifecycle_delta(
490 adapter: &dyn DatabaseOperations,
491 config: &DbBackendConfig,
492 schema: &AppSchema,
493 declaration: &Value,
494 actor_id: i64,
495 before: &Value,
496 after: &Value,
497) -> Result<Value, DatabaseError> {
498 let scope = before
499 .get("_payment_scope")
500 .and_then(Value::as_object)
501 .ok_or(DatabaseError::InvalidInput)?;
502 if scope.get("app_id").and_then(Value::as_str) != Some(schema.app_id.as_str())
503 || scope.get("actor_user_id").and_then(Value::as_i64) != Some(actor_id)
504 || before.get("_payment_scope") != after.get("_payment_scope")
505 {
506 return Err(DatabaseError::InvalidInput);
507 }
508 let mut steps = vec![
509 Mutation::RequireCount {
510 entity: "user_account".to_owned(),
511 filter: Predicate::All(vec![
512 Predicate::Equal("id".to_owned(), Value::from(actor_id)),
513 Predicate::Equal("is_active".to_owned(), Value::Bool(true)),
514 ]),
515 minimum: 1,
516 maximum: Some(1),
517 },
518 payment_authorization_guard(declaration, actor_id, before)?,
519 ];
520 let mut inserted = BTreeMap::new();
521 for &(section, entity_name) in PAYMENT_SECTIONS {
522 let entity = schema
523 .entities
524 .iter()
525 .find(|entity| entity.name == entity_name)
526 .ok_or(DatabaseError::InvalidContract)?;
527 let old_rows = before
528 .get(section)
529 .and_then(Value::as_array)
530 .ok_or(DatabaseError::InvalidInput)?;
531 let new_rows = after
532 .get(section)
533 .and_then(Value::as_array)
534 .ok_or(DatabaseError::InvalidInput)?;
535 let mut old_by_id = BTreeMap::new();
536 for row in old_rows {
537 let id = row
538 .get("id")
539 .and_then(Value::as_i64)
540 .filter(|id| *id > 0)
541 .ok_or(DatabaseError::InvalidInput)?;
542 if old_by_id.insert(id, row).is_some() {
543 return Err(DatabaseError::InvalidInput);
544 }
545 }
546 let mut seen = std::collections::BTreeSet::new();
547 for row in new_rows {
548 let id = row
549 .get("id")
550 .and_then(Value::as_i64)
551 .filter(|id| *id > 0)
552 .ok_or(DatabaseError::InvalidInput)?;
553 if !seen.insert(id) {
554 return Err(DatabaseError::InvalidInput);
555 }
556 let old = old_by_id.remove(&id);
557 if old == Some(row) {
558 continue;
559 }
560 if scope
561 .iter()
562 .filter(|(name, _)| name.as_str() != "actor_user_id")
563 .any(|(name, expected)| row.get(name) != Some(expected))
564 {
565 return Err(DatabaseError::AccessDenied);
566 }
567 let fields = row.as_object().ok_or(DatabaseError::InvalidInput)?;
568 let mut values = BTreeMap::new();
569 for (name, value) in fields {
570 if name == "id" || old.is_some_and(|old| old.get(name) == Some(value)) {
571 continue;
572 }
573 let field = entity
574 .fields
575 .iter()
576 .find(|field| field.name == *name)
577 .ok_or(DatabaseError::InvalidContract)?;
578 let write = if let Some(target_entity) = identity_reference(entity_name, field)
579 && let Some(provisional) = value.as_i64()
580 && let Some(step) = inserted.get(&(target_entity.to_owned(), provisional))
581 {
582 WriteValue::GeneratedId(*step)
583 } else {
584 let value = if field.nullable
585 && field.kind == crate::operations::FieldType::Timestamp
586 && value.as_str() == Some("")
587 {
588 Value::Null
589 } else {
590 value.clone()
591 };
592 WriteValue::Literal(value)
593 };
594 values.insert(name.clone(), write);
595 }
596 if let Some(old) = old {
597 let mut guard = vec![Predicate::Equal("id".to_owned(), Value::from(id))];
598 for name in values.keys() {
599 let previous = old.get(name).ok_or(DatabaseError::InvalidInput)?;
600 guard.push(if previous.is_null() {
601 Predicate::IsNull(name.clone())
602 } else {
603 Predicate::Equal(name.clone(), previous.clone())
604 });
605 }
606 if !values.is_empty() {
607 steps.push(Mutation::Update {
608 entity: entity_name.to_owned(),
609 filter: Predicate::All(guard),
610 values,
611 expected_rows: Some(1),
612 });
613 }
614 } else {
615 inserted.insert((entity_name.to_owned(), id), steps.len());
616 steps.push(Mutation::Insert {
617 entity: entity_name.to_owned(),
618 values,
619 });
620 }
621 }
622 if !old_by_id.is_empty() {
623 return Err(DatabaseError::InvalidInput);
624 }
625 }
626 if steps.len() == 2 {
627 return Ok(after.clone());
628 }
629 let result = adapter.transact(
630 config,
631 schema,
632 &Transaction {
633 steps,
634 idempotency_key: None,
635 },
636 )?;
637 let mut corrected = after.clone();
638 for &(section, entity_name) in PAYMENT_SECTIONS {
639 let entity = schema
640 .entities
641 .iter()
642 .find(|entity| entity.name == entity_name)
643 .ok_or(DatabaseError::InvalidContract)?;
644 let rows = corrected
645 .get_mut(section)
646 .and_then(Value::as_array_mut)
647 .ok_or(DatabaseError::InvalidInput)?;
648 for row in rows {
649 let fields = row.as_object_mut().ok_or(DatabaseError::InvalidInput)?;
650 for field in &entity.fields {
651 let target = if field.name == "id" {
652 Some(entity_name)
653 } else {
654 identity_reference(entity_name, field)
655 };
656 if let Some(target) = target
657 && let Some(provisional) = fields.get(&field.name).and_then(Value::as_i64)
658 && let Some(step) = inserted.get(&(target.to_owned(), provisional))
659 {
660 let actual = result
661 .generated_ids
662 .get(*step)
663 .copied()
664 .flatten()
665 .ok_or(DatabaseError::StorageFailure)?;
666 fields.insert(field.name.clone(), Value::from(actual));
667 }
668 }
669 }
670 }
671 Ok(corrected)
672}
673
674fn payment_authorization_guard(
675 declaration: &Value,
676 actor_id: i64,
677 state: &Value,
678) -> Result<Mutation, DatabaseError> {
679 if actor_id <= 0 {
680 return Err(DatabaseError::InvalidInput);
681 }
682 for (entity_key, section_key, user_key, id_key) in [
683 (
684 "owner_entity",
685 "owner_state_section",
686 "owner_user_id_field",
687 "owner_id_field",
688 ),
689 (
690 "tenant_entity",
691 "tenant_state_section",
692 "tenant_owner_user_id_field",
693 "tenant_id_field",
694 ),
695 ] {
696 let Some(user_field) = declaration.get(user_key).and_then(Value::as_str) else {
697 continue;
698 };
699 let entity = declared_text(declaration, entity_key)?;
700 let section = declared_text(declaration, section_key)?;
701 let id_field = declared_text(declaration, id_key)?;
702 if let Some(row) = state
703 .get(section)
704 .and_then(Value::as_array)
705 .and_then(|rows| rows.first())
706 && row.get(user_field).and_then(Value::as_i64) == Some(actor_id)
707 {
708 let identity = row
709 .get(id_field)
710 .cloned()
711 .ok_or(DatabaseError::InvalidInput)?;
712 return Ok(Mutation::RequireCount {
713 entity: entity.to_owned(),
714 filter: Predicate::All(vec![
715 Predicate::Equal(id_field.to_owned(), identity),
716 Predicate::Equal(user_field.to_owned(), Value::from(actor_id)),
717 ]),
718 minimum: 1,
719 maximum: Some(1),
720 });
721 }
722 }
723 let entity = declared_text(declaration, "membership_entity")?;
724 let section = declared_text(declaration, "membership_state_section")?;
725 let user_field = declared_text(declaration, "membership_user_id_field")?;
726 let tenant_field = declared_text(declaration, "membership_tenant_id_field")?;
727 let status_field = declared_text(declaration, "membership_status_field")?;
728 let row = state
729 .get(section)
730 .and_then(Value::as_array)
731 .and_then(|rows| rows.first())
732 .ok_or(DatabaseError::AccessDenied)?;
733 if row.get(user_field).and_then(Value::as_i64) != Some(actor_id)
734 || row.get(status_field).and_then(Value::as_str) != Some("active")
735 {
736 return Err(DatabaseError::AccessDenied);
737 }
738 let mut filters = vec![
739 Predicate::Equal(user_field.to_owned(), Value::from(actor_id)),
740 Predicate::Equal(
741 tenant_field.to_owned(),
742 row.get(tenant_field)
743 .cloned()
744 .ok_or(DatabaseError::InvalidInput)?,
745 ),
746 Predicate::Equal(status_field.to_owned(), Value::String("active".to_owned())),
747 ];
748 if let Some(field) = declaration
749 .get("membership_role_field")
750 .and_then(Value::as_str)
751 {
752 let role = row
753 .get(field)
754 .and_then(Value::as_str)
755 .ok_or(DatabaseError::AccessDenied)?;
756 let allowed = declaration
757 .get("membership_allowed_roles")
758 .and_then(Value::as_array)
759 .filter(|roles| !roles.is_empty() && roles.len() <= 32)
760 .ok_or(DatabaseError::InvalidContract)?;
761 if !allowed.iter().any(|allowed| allowed.as_str() == Some(role)) {
762 return Err(DatabaseError::AccessDenied);
763 }
764 filters.push(Predicate::Equal(
765 field.to_owned(),
766 Value::String(role.to_owned()),
767 ));
768 }
769 if let Some(field) = declaration
770 .get("membership_revoked_field")
771 .and_then(Value::as_str)
772 {
773 if row.get(field) != Some(&Value::Null) {
774 return Err(DatabaseError::AccessDenied);
775 }
776 filters.push(Predicate::IsNull(field.to_owned()));
777 }
778 Ok(Mutation::RequireCount {
779 entity: entity.to_owned(),
780 filter: Predicate::All(filters),
781 minimum: 1,
782 maximum: Some(1),
783 })
784}