Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 36 additions & 3 deletions src/storage/base-storage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -183,10 +183,43 @@ export abstract class BaseStorage {
}
break;

case FieldType.GEO:
// TODO: Implement geo validation
// Geo fields should be in format "longitude,latitude"
case FieldType.GEO: {
if (typeof value !== 'string') {
throw new SchemaValidationError(
`Field '${fieldName}' should be a string in "longitude,latitude" format but got: ${typeof value}`
);
}

const parts = value.trim().split(',');

if (parts.length !== 2) {
throw new SchemaValidationError(
`Field '${fieldName}' should be in "longitude,latitude" format but got: "${value}"`
);
}

const longitude = parseFloat(parts[0].trim());
const latitude = parseFloat(parts[1].trim());

if (isNaN(longitude) || isNaN(latitude)) {
throw new SchemaValidationError(
`Field '${fieldName}' contains invalid coordinates. Both longitude and latitude must be valid numbers. Got: "${value}"`
);
}

if (longitude < -180 || longitude > 180) {
throw new SchemaValidationError(
`Field '${fieldName}' longitude must be between -180 and 180, got: ${longitude}`
);
}

if (latitude < -90 || latitude > 90) {
throw new SchemaValidationError(
`Field '${fieldName}' latitude must be between -90 and 90, got: ${latitude}`
);
}
break;
}
}
}

Expand Down
82 changes: 82 additions & 0 deletions tests/unit/storage/base-storage-geo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
import { describe, it, expect } from 'vitest';
import { IndexSchema, IndexInfo } from '../../../src/schema/schema.js';
import { BaseStorage } from '../../../src/storage/base-storage.js';
import { SchemaValidationError } from '../../../src/errors.js';

// Test double — exposes protected validate() for unit testing
class TestStorage extends BaseStorage {
public testValidate(doc: Record<string, unknown>) {
return this.validate(doc);
}
async write() {
return [];
}
async get() {
return [];
}
}

// Minimal schema with a geo field
function makeGeoSchema() {
const index = new IndexInfo({ name: 'test-geo-index' });
const schema = new IndexSchema({ index });
schema.addField({ name: 'location', type: 'geo' });
return schema;
}

describe('BaseStorage geo field validation', () => {
describe('valid geo values', () => {
it('should accept "0,0"', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: '0,0' })).not.toThrow();
});

it('should accept boundary values "-180,-90"', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: '-180,-90' })).not.toThrow();
});

it('should accept boundary values "180,90"', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: '180,90' })).not.toThrow();
});

it('should accept realistic coordinates', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: '40.7128,-74.0060' })).not.toThrow();
});
});

describe('invalid geo values', () => {
it('should reject a non-string value', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: 12345 })).toThrow(SchemaValidationError);
});

it('should reject a value with no comma', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: '12345' })).toThrow(
SchemaValidationError
);
});

it('should reject non-numeric parts', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: 'abc,def' })).toThrow(
SchemaValidationError
);
});

it('should reject longitude out of range', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: '181,0' })).toThrow(
SchemaValidationError
);
});

it('should reject latitude out of range', () => {
const storage = new TestStorage(makeGeoSchema());
expect(() => storage.testValidate({ location: '0,91' })).toThrow(SchemaValidationError);
});
});
});