Skip to content

Commit

Permalink
refactor: move all exported functions to /api
Browse files Browse the repository at this point in the history
  • Loading branch information
MichaelSolati committed Jun 28, 2020
1 parent b6f64e7 commit 246cc64
Show file tree
Hide file tree
Showing 7 changed files with 269 additions and 262 deletions.
94 changes: 94 additions & 0 deletions src/api/encode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import {hash} from 'geokit';
import {validateLocation} from './validate';
import {GeoFirestoreTypes} from '../definitions';
import {findGeoPoint} from '../utils';

/**
* Encodes a Firestore Document to be added as a GeoDocument.
*
* @param documentData The document being set.
* @param customKey The key of the document to use as the location. Otherwise we default to `coordinates`.
* @return The document encoded as GeoDocument object.
*/
export function encodeDocumentAdd(
documentData: GeoFirestoreTypes.DocumentData,
customKey?: string
): GeoFirestoreTypes.GeoDocumentData {
if (Object.prototype.toString.call(documentData) !== '[object Object]') {
throw new Error('document must be an object');
}
const geopoint = findGeoPoint(documentData, customKey);
return encodeGeoDocument(geopoint, documentData);
}

/**
* Encodes a Firestore Document to be set as a GeoDocument.
*
* @param documentData A map of the fields and values for the document.
* @param options An object to configure the set behavior. Includes custom key for location in document.
* @return The document encoded as GeoDocument object.
*/
export function encodeDocumentSet(
documentData: GeoFirestoreTypes.DocumentData,
options?: GeoFirestoreTypes.SetOptions
): GeoFirestoreTypes.GeoDocumentData | GeoFirestoreTypes.DocumentData {
if (Object.prototype.toString.call(documentData) !== '[object Object]') {
throw new Error('document must be an object');
}
const customKey = options && options.customKey;
const geopoint = findGeoPoint(
documentData,
customKey,
options && (options.merge || !!options.mergeFields)
);
if (geopoint) {
return encodeGeoDocument(geopoint, documentData);
}
return documentData;
}

/**
* Encodes a Firestore Document to be updated as a GeoDocument.
*
* @param documentData The document being updated.
* @param customKey The key of the document to use as the location. Otherwise we default to `coordinates`.
* @return The document encoded as GeoDocument object.
*/
export function encodeDocumentUpdate(
documentData: GeoFirestoreTypes.UpdateData,
customKey?: string
): GeoFirestoreTypes.UpdateData {
if (Object.prototype.toString.call(documentData) !== '[object Object]') {
throw new Error('document must be an object');
}
const geopoint = findGeoPoint(documentData, customKey, true);
if (geopoint) {
documentData = encodeGeoDocument(geopoint, documentData);
}
return documentData;
}

/**
* Encodes a document with a GeoPoint as a GeoDocument.
*
* @param geopoint The location as a Firestore GeoPoint.
* @param documentData Document to encode.
* @return The document encoded as GeoDocument object.
*/
export function encodeGeoDocument(
geopoint: GeoFirestoreTypes.cloud.GeoPoint | GeoFirestoreTypes.web.GeoPoint,
documentData: GeoFirestoreTypes.DocumentData
): GeoFirestoreTypes.GeoDocumentData {
validateLocation(geopoint);
const geohash = hash({
lat: geopoint.latitude,
lng: geopoint.longitude,
});
return {
...documentData,
g: {
geopoint,
geohash,
},
};
}
7 changes: 2 additions & 5 deletions src/api/query-get.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
import {GeoQuerySnapshot} from './snapshot';
import {validateQueryCriteria} from './validate';
import {GeoFirestoreTypes} from '../definitions';
import {
calculateDistance,
generateQuery,
validateQueryCriteria,
} from '../utils';
import {calculateDistance, generateQuery} from '../utils';

/**
* Executes a query and returns the result(s) as a GeoQuerySnapshot.
Expand Down
10 changes: 3 additions & 7 deletions src/api/query-on-snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import {GeoFirestoreTypes} from '../definitions';
import {GeoQuerySnapshot} from './snapshot';
import {
calculateDistance,
generateQuery,
validateQueryCriteria,
validateGeoDocument,
} from '../utils';
import {validateGeoDocument, validateQueryCriteria} from './validate';
import {GeoFirestoreTypes} from '../definitions';
import {calculateDistance, generateQuery} from '../utils';

interface DocMap {
change: GeoFirestoreTypes.web.DocumentChange;
Expand Down
3 changes: 2 additions & 1 deletion src/api/snapshot.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import {validateLocation} from './validate';
import {GeoFirestoreTypes} from '../definitions';
import {generateGeoQueryDocumentSnapshot, validateLocation} from '../utils';
import {generateGeoQueryDocumentSnapshot} from '../utils';

/**
* A `GeoQuerySnapshot` contains zero or more `QueryDocumentSnapshot` objects
Expand Down
156 changes: 156 additions & 0 deletions src/api/validate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import {validateHash} from 'geokit';
import {GeoFirestoreTypes} from '../definitions';

/**
* Validates a GeoPoint object and returns a boolean if valid, or throws an error if invalid.
*
* @param location The Firestore GeoPoint to be verified.
* @param flag Tells function to send up boolean if not valid instead of throwing an error.
*/
export function validateLocation(
location: GeoFirestoreTypes.web.GeoPoint | GeoFirestoreTypes.cloud.GeoPoint,
flag = false
): boolean {
let error: string;

if (!location) {
error = 'GeoPoint must exist';
} else if (typeof location.latitude === 'undefined') {
error = 'latitude must exist on GeoPoint';
} else if (typeof location.longitude === 'undefined') {
error = 'longitude must exist on GeoPoint';
} else {
const latitude = location.latitude;
const longitude = location.longitude;

if (typeof latitude !== 'number' || isNaN(latitude)) {
error = 'latitude must be a number';
} else if (latitude < -90 || latitude > 90) {
error = 'latitude must be within the range [-90, 90]';
} else if (typeof longitude !== 'number' || isNaN(longitude)) {
error = 'longitude must be a number';
} else if (longitude < -180 || longitude > 180) {
error = 'longitude must be within the range [-180, 180]';
}
}

if (typeof error !== 'undefined' && !flag) {
throw new Error('Invalid location: ' + error);
} else {
return !error;
}
}

/**
* Validates the inputted limit and throws an error, or returns boolean, if it is invalid.
*
* @param limit The limit to be applied by `GeoQuery.limit()`
* @param flag Tells function to send up boolean if valid instead of throwing an error.
*/
export function validateLimit(limit: number, flag = false): boolean {
let error: string;
if (typeof limit !== 'number' || isNaN(limit)) {
error = 'limit must be a number';
} else if (limit < 0) {
error = 'limit must be greater than or equal to 0';
}

if (typeof error !== 'undefined' && !flag) {
throw new Error(error);
} else {
return !error;
}
}

/**
* Validates the inputted GeoDocument object and throws an error, or returns boolean, if it is invalid.
*
* @param documentData The GeoDocument object to be validated.
* @param flag Tells function to send up boolean if valid instead of throwing an error.
* @return Flag if data is valid
*/
export function validateGeoDocument(
documentData: GeoFirestoreTypes.GeoDocumentData,
flag = false
): boolean {
let error: string;

if (!documentData) {
error = 'no document found';
} else if ('g' in documentData) {
error = !validateHash(documentData.g.geohash, true)
? 'invalid geohash on object'
: null;
error = !validateLocation(documentData.g.geopoint, true)
? 'invalid location on object'
: error;
} else {
error = 'no `g` field found in object';
}

if (error && !flag) {
throw new Error('Invalid GeoFirestore object: ' + error);
} else {
return !error;
}
}

/**
* Validates the inputted query criteria and throws an error if it is invalid.
*
* @param newQueryCriteria The criteria which specifies the query's center and/or radius.
* @param requireCenterAndRadius The criteria which center and radius required.
*/
export function validateQueryCriteria(
newQueryCriteria: GeoFirestoreTypes.QueryCriteria,
requireCenterAndRadius = false
): void {
if (typeof newQueryCriteria !== 'object') {
throw new Error('QueryCriteria must be an object');
} else if (
typeof newQueryCriteria.center === 'undefined' &&
typeof newQueryCriteria.radius === 'undefined'
) {
throw new Error('radius and/or center must be specified');
} else if (
requireCenterAndRadius &&
(typeof newQueryCriteria.center === 'undefined' ||
typeof newQueryCriteria.radius === 'undefined')
) {
throw new Error(
'QueryCriteria for a new query must contain both a center and a radius'
);
}

// Throw an error if there are any extraneous attributes
const keys: string[] = Object.keys(newQueryCriteria);
for (const key of keys) {
if (!['center', 'radius', 'limit'].includes(key)) {
throw new Error(
"Unexpected attribute '" + key + "' found in query criteria"
);
}
}

// Validate the 'center' attribute
if (typeof newQueryCriteria.center !== 'undefined') {
validateLocation(newQueryCriteria.center);
}

// Validate the 'radius' attribute
if (typeof newQueryCriteria.radius !== 'undefined') {
if (
typeof newQueryCriteria.radius !== 'number' ||
isNaN(newQueryCriteria.radius)
) {
throw new Error('radius must be a number');
} else if (newQueryCriteria.radius < 0) {
throw new Error('radius must be greater than or equal to 0');
}
}

// Validate the 'limit' attribute
if (typeof newQueryCriteria.limit !== 'undefined') {
validateLimit(newQueryCriteria.limit);
}
}
15 changes: 10 additions & 5 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
export {geoQueryGet} from './api/query-get';
export {geoQueryOnSnapshot} from './api/query-on-snapshot';
export {GeoQuerySnapshot} from './api/snapshot';
export * from './definitions';
export {
encodeDocumentAdd,
encodeDocumentSet,
encodeDocumentUpdate,
encodeGeoDocument,
} from './api/encode';
export {geoQueryGet} from './api/query-get';
export {geoQueryOnSnapshot} from './api/query-on-snapshot';
export {GeoQuerySnapshot} from './api/snapshot';
export {
validateLocation,
validateLimit,
validateGeoDocument,
validateQueryCriteria,
} from './utils';
} from './api/validate';
export * from './definitions';
Loading

0 comments on commit 246cc64

Please sign in to comment.