Skip to content

Commit

Permalink
fix: incorrect parsing of geopoint data type
Browse files Browse the repository at this point in the history
- resolve #84.
- feat: parse abbreviated field name `lat` & `lng`.
- parse object into geopoint type if object only contains `latitude`, `longitude` and `geohash` fields.
  • Loading branch information
phiHero committed Aug 30, 2024
1 parent b9b89bf commit df51edf
Show file tree
Hide file tree
Showing 2 changed files with 103 additions and 34 deletions.
19 changes: 14 additions & 5 deletions functions/src/utils.js
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
const config = require("./config.js");

const mapValue = (value) => {
if (typeof value === "object" && value !== null && value.seconds != null && value.nanoseconds != null) {
const isObject = typeof value === 'object';
const notNull = value !== null;
const length = Object.keys(value).length;

const latitude = value.latitude ?? value.lat;
const longitude = value.longitude ?? value.lng;
const hasGeohashField = value.geohash != null && length == 3;
const isGeopointType = latitude != null && longitude != null && (length == 2 || hasGeohashField);

if (isObject && notNull && value.seconds != null && value.nanoseconds != null) {
// convert date to Unix timestamp
// https://typesense.org/docs/0.22.2/api/collections.html#indexing-dates
return Math.floor(value.toDate().getTime() / 1000);
} else if (typeof value === "object" && value !== null && value.latitude != null && value.longitude != null) {
return [value.latitude, value.longitude];
} else if (typeof value === "object" && value !== null && value.firestore != null && value.path != null) {
} else if (isObject && notNull && isGeopointType) {
return [latitude, longitude];
} else if (isObject && notNull && value.firestore != null && value.path != null) {
return {"path": value.path};
} else if (Array.isArray(value)) {
return value.map(mapValue);
} else if (typeof value === "object" && value !== null) {
} else if (isObject && notNull) {
return Object.fromEntries(Object.entries(value).map(([key, value]) => [key, mapValue(value)]));
} else {
return value;
Expand Down
118 changes: 89 additions & 29 deletions test/utils.spec.js
Original file line number Diff line number Diff line change
@@ -1,45 +1,105 @@
const test = require("firebase-functions-test")({
const test = require('firebase-functions-test')({
projectId: process.env.GCLOUD_PROJECT,
});

describe("Utils", () => {
describe("typesenseDocumentFromSnapshot", () => {
describe("when document fields are mentioned explicitly", () => {
it("returns a Typesense document with only the specified fields", async () => {
const typesenseDocumentFromSnapshot =
(await import("../functions/src/utils.js")).typesenseDocumentFromSnapshot;
describe('Utils', () => {
describe('typesenseDocumentFromSnapshot', () => {
describe('when document fields are mentioned explicitly', () => {
it('returns a Typesense document with only the specified fields', async () => {
const typesenseDocumentFromSnapshot = (
await import('../functions/src/utils.js')
).typesenseDocumentFromSnapshot;

const documentSnapshot = test.firestore.makeDocumentSnapshot({
author: "Author X",
title: "Title X",
country: "USA",
}, "id");
const documentSnapshot = test.firestore.makeDocumentSnapshot(
{
author: 'Author X',
title: 'Title X',
country: 'USA',
},
'id'
);

const result = await typesenseDocumentFromSnapshot(documentSnapshot);
expect(result).toEqual({
id: "id",
author: "Author X",
title: "Title X",
id: 'id',
author: 'Author X',
title: 'Title X',
});
});
});
describe("when no fields are mentioned explicitly", () => {
it("returns a Typesense document with all fields", async () => {
const typesenseDocumentFromSnapshot =
(await import("../functions/src/utils.js")).typesenseDocumentFromSnapshot;

const documentSnapshot = test.firestore.makeDocumentSnapshot({
author: "Author X",
title: "Title X",
country: "USA",
}, "id");
describe('when no fields are mentioned explicitly', () => {
it('returns a Typesense document with all fields', async () => {
const typesenseDocumentFromSnapshot = (
await import('../functions/src/utils.js')
).typesenseDocumentFromSnapshot;

const result = await typesenseDocumentFromSnapshot(documentSnapshot, []);
const documentSnapshot = test.firestore.makeDocumentSnapshot(
{
author: 'Author X',
title: 'Title X',
country: 'USA',
},
'id'
);

const result = await typesenseDocumentFromSnapshot(
documentSnapshot,
[]
);
expect(result).toEqual({
id: 'id',
author: 'Author X',
title: 'Title X',
country: 'USA',
});
});
});

it('Can parse geopoint datatype', async () => {
const typesenseDocumentFromSnapshot = (
await import('../functions/src/utils.js')
).typesenseDocumentFromSnapshot;
const data = [
{
location: {
latitude: 1,
longitude: 2,
},
},
{
location: {
lat: 1,
lng: 2,
},
},
{
location: {
geohash: 'abc',
latitude: 1,
longitude: 2,
},
},
{
location: {
geohash: 'abc',
lat: 1,
lng: 2,
},
},
];
data.forEach(async (item) => {
const documentSnapshot = test.firestore.makeDocumentSnapshot(
item,
'id'
);
const result = await typesenseDocumentFromSnapshot(
documentSnapshot,
[]
);
expect(result).toEqual({
id: "id",
author: "Author X",
title: "Title X",
country: "USA",
id: 'id',
location: [1, 2],
});
});
});
Expand Down

0 comments on commit df51edf

Please sign in to comment.