1
+ /**
2
+ * @license
3
+ * Copyright 2025 Google LLC
4
+ *
5
+ * Licensed under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License.
7
+ * You may obtain a copy of the License at
8
+ *
9
+ * http://www.apache.org/licenses/LICENSE-2.0
10
+ *
11
+ * Unless required by applicable law or agreed to in writing, software
12
+ * distributed under the License is distributed on an "AS IS" BASIS,
13
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ * See the License for the specific language governing permissions and
15
+ * limitations under the License.
16
+ */
17
+
18
+ import { isPlainObject } from '../util/input_validation' ;
19
+
20
+ import { Code , FirestoreError } from './error' ;
21
+
22
+ /** A list of data types Firestore objects may serialize in their toJSON implemenetations. */
23
+ export type JsonTypeDesc = "string" | "number" | "boolean" | "null" | "undefined" ;
24
+
25
+ /** An association of JsonTypeDesc values to their native types. */
26
+ type TSType < T extends JsonTypeDesc > =
27
+ T extends "string" ? string :
28
+ T extends "number" ? number :
29
+ T extends "boolean" ? boolean :
30
+ T extends "null" ? null :
31
+ T extends "undefined" ? undefined :
32
+ never ;
33
+
34
+ /** The representation of a JSON object property name and its type value. */
35
+ export interface Property < T extends JsonTypeDesc > {
36
+ value ?: TSType < T > ;
37
+ typeString : JsonTypeDesc ;
38
+ } ;
39
+
40
+ /** A type Firestore data types may use to define the fields used in their JSON serialization. */
41
+ export interface JsonSchema {
42
+ [ key : string ] : Property < JsonTypeDesc > ;
43
+ } ;
44
+
45
+ /** Associates the JSON property type to the native type and sets them to be Required. */
46
+ export type Json < T extends JsonSchema > = {
47
+ [ K in keyof T ] : Required < T [ K ] > [ 'value' ]
48
+ } ;
49
+
50
+ /** Helper function to define a JSON schema {@link Property} */
51
+ export function property < T extends JsonTypeDesc > ( typeString : T , optionalValue ?: TSType < T > ) : Property < T > {
52
+ const result : Property < T > = {
53
+ typeString
54
+ } ;
55
+ if ( optionalValue ) {
56
+ result . value = optionalValue ;
57
+ }
58
+ return result ;
59
+ } ;
60
+
61
+ /** Validates the JSON object based on the provided schema, and narrows the type to the provided
62
+ * JSON schaem.
63
+ *
64
+ * @param json A JSON object to validate.
65
+ * @param scheme a {@link JsonSchema} that defines the properties to validate.
66
+ * @returns true if the JSON schema exists within the object. Throws a FirestoreError otherwise.
67
+ */
68
+ export function validateJSON < S extends JsonSchema > ( json : object , schema : S ) : json is Json < S > {
69
+ if ( ! isPlainObject ( json ) ) {
70
+ throw new FirestoreError ( Code . INVALID_ARGUMENT , "json must be an object" ) ;
71
+ }
72
+ let error : string | undefined = undefined ;
73
+ for ( const key in schema ) {
74
+ if ( schema [ key ] ) {
75
+ const typeString = schema [ key ] . typeString ;
76
+ const value : { value : unknown } | undefined = ( 'value' in schema [ key ] ) ? { value : schema [ key ] . value } : undefined ;
77
+ if ( ! ( key in json ) ) {
78
+ error = `json missing required field: ${ key } ` ;
79
+ }
80
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
81
+ const fieldValue = ( json as any ) [ key ] ;
82
+ if ( typeString && ( ( typeof fieldValue ) !== typeString ) ) {
83
+ error = `json field '${ key } ' must be a ${ typeString } .` ;
84
+ break ;
85
+ } else if ( ( value !== undefined ) && fieldValue !== value . value ) {
86
+ error = `Expected '${ key } ' field to equal '${ value . value } '` ;
87
+ break ;
88
+ }
89
+ }
90
+ }
91
+ if ( error ) {
92
+ throw new FirestoreError ( Code . INVALID_ARGUMENT , error ) ;
93
+ }
94
+ return true ;
95
+ }
0 commit comments