Skip to content

Commit

Permalink
feat: aggregation validation (#238)
Browse files Browse the repository at this point in the history
* refactor: additional regex patterns and organize props alphabetically

* refactor: use validation pattern

* feat: aggregation zod schema

* refactor: add time validation between begin, end and current timestamp

* fix: openapi sensor regex pattern

* fix: file name pattern validation

* style: remove extra spaces

* refactor: improve error messages

* refactor: removed prefix of each error message

* refactor: error messages for part data zod schema
  • Loading branch information
vitaligi authored Nov 26, 2024
1 parent ba608ec commit c9f8a64
Show file tree
Hide file tree
Showing 6 changed files with 168 additions and 32 deletions.
26 changes: 16 additions & 10 deletions src/models/raster/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@
import { zoomLevelToResolutionDeg, zoomLevelToResolutionMeter } from '@map-colonies/mc-utils';

export const VALIDATIONS = {
resolutionMeter: {
min: zoomLevelToResolutionMeter(22),
max: zoomLevelToResolutionMeter(0),
boundingBox: {
pattern: '^-?(0|[1-9]\\d*)(\\.\\d*)?,-?(0|[1-9]\\d*)(\\.\\d*)?,-?(0|[1-9]\\d*)(\\.\\d*)?,-?(0|[1-9]\\d*)(\\.\\d*)?$',
},
resolutionDeg: {
min: zoomLevelToResolutionDeg(22),
max: zoomLevelToResolutionDeg(0),
classification: {
pattern: '^[0-9]$|^[1-9][0-9]$|^(100)$',
},
fileNames: {
pattern: '^.+\\.[Gg][Pp][Kk][Gg]$',
},
horizontalAccuracyCE90: {
min: 0.01,
Expand All @@ -20,14 +21,19 @@ export const VALIDATIONS = {
productVersion: {
pattern: '^[1-9]\\d*(\\.(0|[1-9]\\d?))?$',
},
classification: {
pattern: '^[0-9]$|^[1-9][0-9]$|^(100)$',
resolutionDeg: {
min: zoomLevelToResolutionDeg(22),
max: zoomLevelToResolutionDeg(0),
},
resolutionMeter: {
min: zoomLevelToResolutionMeter(22),
max: zoomLevelToResolutionMeter(0),
},
scale: {
min: 0,
max: 100000000,
},
fileNames: {
pattern: '^.+.[Gg][Pp][Kk][Gg]$',
sensor: {
pattern: '^(?!\\s).+(?<!\\s)$',
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import type { MultiPolygon, Polygon } from 'geojson';
import { z, type ZodType } from 'zod';
import type { AggregationLayerMetadata } from '../../../../polygonParts/aggregationLayerMetadata';
import { VALIDATIONS } from '../../../constants';

export const aggregationLayerMetadataSchema: ZodType<AggregationLayerMetadata> = z
.object(
{
footprint: z.custom<Polygon | MultiPolygon>(),
imagingTimeBeginUTC: z.coerce.date({ message: 'Imaging time begin UTC should be a datetime' }),
imagingTimeEndUTC: z.coerce.date({ message: 'Imaging time end UTC should be a datetime' }),
maxHorizontalAccuracyCE90: z
.number({ message: 'Max horizontal accuracy CE90 should be a number' })
.min(VALIDATIONS.horizontalAccuracyCE90.min, {
message: `Max horizontal accuracy CE90 should not be less than ${VALIDATIONS.horizontalAccuracyCE90.min}`,
})
.max(VALIDATIONS.horizontalAccuracyCE90.max, {
message: `Max horizontal accuracy CE90 should not be larger than ${VALIDATIONS.horizontalAccuracyCE90.max}`,
}),
maxResolutionDeg: z
.number({ message: 'Max resolution degree should be a number' })
.min(VALIDATIONS.resolutionDeg.min as number, {
message: `Max resolution degree should not be less than ${VALIDATIONS.resolutionDeg.min as number}`,
})
.max(VALIDATIONS.resolutionDeg.max as number, {
message: `Max resolution degree should not be larger than ${VALIDATIONS.resolutionDeg.max as number}`,
}),
maxResolutionMeter: z
.number({ message: 'Max resolution meter should be a number' })
.min(VALIDATIONS.resolutionMeter.min as number, {
message: `Max resolution meter should not be less than ${VALIDATIONS.resolutionMeter.min as number}`,
})
.max(VALIDATIONS.resolutionMeter.max as number, {
message: `Max resolution meter should not be larger than ${VALIDATIONS.resolutionMeter.max as number}`,
}),
minHorizontalAccuracyCE90: z
.number({ message: 'Min horizontal accuracy CE90 should be a number' })
.min(VALIDATIONS.horizontalAccuracyCE90.min, {
message: `Min horizontal accuracy CE90 should not be less than ${VALIDATIONS.horizontalAccuracyCE90.min}`,
})
.max(VALIDATIONS.horizontalAccuracyCE90.max, {
message: `Min horizontal accuracy CE90 should not be larger than ${VALIDATIONS.horizontalAccuracyCE90.max}`,
}),
minResolutionDeg: z
.number({ message: 'Min resolution degree should be a number' })
.min(VALIDATIONS.resolutionDeg.min as number, {
message: `Min resolution degree should not be less than ${VALIDATIONS.resolutionDeg.min as number}`,
})
.max(VALIDATIONS.resolutionDeg.max as number, {
message: `Min resolution degree should not be larger than ${VALIDATIONS.resolutionDeg.max as number}`,
}),
minResolutionMeter: z
.number({ message: 'Min resolution meter should be a number' })
.min(VALIDATIONS.resolutionMeter.min as number, {
message: `Min resolution meter should not be less than ${VALIDATIONS.resolutionMeter.min as number}`,
})
.max(VALIDATIONS.resolutionMeter.max as number, {
message: `Min resolution meter should not be larger than ${VALIDATIONS.resolutionMeter.max as number}`,
}),
productBoundingBox: z.string({ message: 'Product bounding box should be a string' }).regex(new RegExp(VALIDATIONS.boundingBox.pattern), {
message: 'Product bounding box must be of the shape min_x,min_y,max_x,max_y',
}),
sensors: z
.array(
z.string({ message: 'Sensors should be an array of strings' }).regex(new RegExp(VALIDATIONS.sensor.pattern), {
message: 'Sensors should be an array with items not starting or ending with whitespace characters',
}),
{ message: 'Sensors should be an array' }
)
.min(1, { message: 'Sensors should have an array length of at least 1' }),
},
{ message: 'Layer metadata should be an object' }
)
.strict()
.refine(
(aggregationLayerMetadata) =>
aggregationLayerMetadata.imagingTimeBeginUTC <= aggregationLayerMetadata.imagingTimeEndUTC &&
aggregationLayerMetadata.imagingTimeEndUTC <= new Date(),
{
message: 'Imaging time begin UTC should be less than or equal to imaging time end UTC and both less than or equal to current timestamp',
}
)
.refine((aggregationLayerMetadata) => aggregationLayerMetadata.minHorizontalAccuracyCE90 <= aggregationLayerMetadata.maxHorizontalAccuracyCE90, {
message: 'Min horizontal accuracy CE90 should be less than or equal to max horizontal accuracy CE90',
})
.refine((aggregationLayerMetadata) => aggregationLayerMetadata.minResolutionDeg <= aggregationLayerMetadata.maxResolutionDeg, {
message: 'Min resolution degree should be less than or equal to max resolution degree',
})
.refine((aggregationLayerMetadata) => aggregationLayerMetadata.minResolutionMeter <= aggregationLayerMetadata.maxResolutionMeter, {
message: 'Min resolution meter should be less than or equal to max resolution meter',
})
.describe('aggregationLayerMetadataSchema');
3 changes: 2 additions & 1 deletion src/models/raster/ingestion/zod/schemas/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './partData.schema';
export * from './aggregationLayerMetadata.schema';
export * from './inputFiles.schema';
export * from './metadata.schema';
export * from './partData.schema';
75 changes: 56 additions & 19 deletions src/models/raster/ingestion/zod/schemas/partData.schema.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,68 @@
/* eslint-disable @typescript-eslint/no-magic-numbers */
import type { Polygon } from 'geojson';
import { z } from 'zod';
import { Polygon } from 'geojson';
import { VALIDATIONS } from '../../../constants';

export const partSchema = z
.object({
sourceId: z.string().optional(),
sourceName: z.string().min(1),
description: z.string().optional(),
imagingTimeBeginUTC: z.coerce.date(),
imagingTimeEndUTC: z.coerce.date(),
sourceId: z.string({ message: 'Source id should be a string' }).optional(),
sourceName: z.string({ message: 'Source name should be a string' }).min(1, { message: 'Source name should have length of at least 1' }),
description: z.string({ message: 'Description should be a string' }).optional(),
imagingTimeBeginUTC: z.coerce.date({ message: 'Imaging time begin UTC should be a datetime' }),
imagingTimeEndUTC: z.coerce.date({ message: 'Imaging time end UTC should be a datetime' }),
resolutionDegree: z
.number()
.min(VALIDATIONS.resolutionDeg.min as number)
.max(VALIDATIONS.resolutionDeg.max as number),
.number({ message: 'Resolution degree should be a number' })
.min(VALIDATIONS.resolutionDeg.min as number, {
message: `Resolution degree should not be less than ${VALIDATIONS.resolutionDeg.min as number}`,
})
.max(VALIDATIONS.resolutionDeg.max as number, {
message: `Resolution degree should not be larger than ${VALIDATIONS.resolutionDeg.max as number}`,
}),
resolutionMeter: z
.number()
.min(VALIDATIONS.resolutionMeter.min as number)
.max(VALIDATIONS.resolutionMeter.max as number),
.number({ message: 'Resolution meter should be a number' })
.min(VALIDATIONS.resolutionMeter.min as number, {
message: `Resolution meter should not be less than ${VALIDATIONS.resolutionMeter.min as number}`,
})
.max(VALIDATIONS.resolutionMeter.max as number, {
message: `Resolution meter should not be larger than ${VALIDATIONS.resolutionMeter.max as number}`,
}),
sourceResolutionMeter: z
.number()
.min(VALIDATIONS.resolutionMeter.min as number)
.max(VALIDATIONS.resolutionMeter.max as number),
horizontalAccuracyCE90: z.number().min(VALIDATIONS.horizontalAccuracyCE90.min).max(VALIDATIONS.horizontalAccuracyCE90.max),
sensors: z.array(z.string().regex(new RegExp('^(?! ).+(?<! )$'))).min(1),
countries: z.array(z.string().min(1)).optional(),
cities: z.array(z.string().min(1)).optional(),
.number({ message: 'Source resolution meter should be a number' })
.min(VALIDATIONS.resolutionMeter.min as number, {
message: `Source resolution meter should not be less than ${VALIDATIONS.resolutionMeter.min as number}`,
})
.max(VALIDATIONS.resolutionMeter.max as number, {
message: `Source resolution meter should not be larger than ${VALIDATIONS.resolutionMeter.max as number}`,
}),
horizontalAccuracyCE90: z
.number({ message: 'Horizontal accuracy CE90 should be a number' })
.min(VALIDATIONS.horizontalAccuracyCE90.min, {
message: `Horizontal accuracy CE90 should not be less than ${VALIDATIONS.horizontalAccuracyCE90.min}`,
})
.max(VALIDATIONS.horizontalAccuracyCE90.max, {
message: `Horizontal accuracy CE90 should not be larger than ${VALIDATIONS.horizontalAccuracyCE90.max}`,
}),
sensors: z
.array(
z.string({ message: 'Sensors should be an array of strings' }).regex(new RegExp(VALIDATIONS.sensor.pattern), {
message: 'Sensors should be an array with items not starting or ending with whitespace characters',
}),
{ message: 'Sensors should be an array' }
)
.min(1, { message: 'Sensors should have an array length of at least 1' }),
countries: z
.array(z.string({ message: 'Countries should be an array of strings' }).min(1, { message: 'Countries should have length of at least 1' }), {
message: 'Countries should be an array',
})
.optional(),
cities: z
.array(z.string({ message: 'Cities should be an array of strings' }).min(1, { message: 'Cities should have length of at least 1' }), {
message: 'Cities should be an array',
})
.optional(),
footprint: z.custom<Polygon>(),
})
.refine((part) => part.imagingTimeBeginUTC <= part.imagingTimeEndUTC && part.imagingTimeEndUTC <= new Date(), {
message: 'Imaging time begin UTC should be less than or equal to imaging time end UTC and both less than or equal to current timestamp',
})
.describe('partSchema');
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,4 @@ components:
example: ['example.gpkg']
required:
- originDirectory
- fileNames
- fileNames
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ components:
type: array
items:
type: string
pattern: ^(?! ).+(?<! )$
pattern: ^(?!\s).+(?<!\s)$
minItems: 1
description: Layer sensors list
countries:
Expand Down

0 comments on commit c9f8a64

Please sign in to comment.