Skip to content

Commit b753f49

Browse files
committed
Linter doesn't like it, but API extractor does
1 parent 775cc69 commit b753f49

File tree

2 files changed

+109
-24
lines changed

2 files changed

+109
-24
lines changed

packages/firestore/src/lite-api/bytes.ts

Lines changed: 14 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import { ByteString } from '../util/byte_string';
1919
import { Code, FirestoreError } from '../util/error';
20+
import { JsonTypeDesc, Property, property, validateJSON } from '../util/json_validation';
2021

2122
/**
2223
* An immutable object representing an array of bytes.
@@ -92,39 +93,28 @@ export class Bytes {
9293
return this._byteString.isEqual(other._byteString);
9394
}
9495

96+
static _jsonSchemaVersion: string = 'firestore/bytes/1.0';
97+
static _jsonSchema = {
98+
type: property('string', Bytes._jsonSchemaVersion),
99+
bytes: property('string')
100+
};
101+
95102
/** Returns a JSON-serializable representation of this `Bytes` instance. */
96103
toJSON(): object {
97104
return {
98-
type: 'firestore/bytes/1.0',
105+
type: Bytes._jsonSchemaVersion,
99106
bytes: this.toBase64()
100107
};
101108
}
102109

103110
/** Builds a `Bytes` instance from a JSON serialized version of `Bytes`. */
104111
static fromJSON(json: object): Bytes {
105-
const requiredFields = ['type', 'bytes'];
106-
let error: string | undefined = undefined;
107-
let bytesData: string = '';
108-
for (const key of requiredFields) {
109-
if (!(key in json)) {
110-
error = `json missing required field: ${key}`;
111-
break;
112-
}
113-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
114-
const value = (json as any)[key];
115-
if (typeof value !== 'string') {
116-
error = `json field '${key}' must be a string.`;
117-
break;
118-
} else if (key === 'type' && value !== 'firestore/bytes/1.0') {
119-
error = "Expected 'type' field to equal 'firestore/bytes/1.0'";
120-
break;
121-
} else if (key === 'bytes') {
122-
bytesData = value;
123-
}
124-
}
125-
if (error) {
126-
throw new FirestoreError(Code.INVALID_ARGUMENT, error);
112+
if (validateJSON(json, Bytes._jsonSchema)) {
113+
return Bytes.fromBase64String(json.bytes);
127114
}
128-
return Bytes.fromBase64String(bytesData);
115+
throw new FirestoreError(
116+
Code.INTERNAL,
117+
'Unexpected error creating Bytes from JSON.'
118+
);
129119
}
130120
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
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

Comments
 (0)