Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: aggregation validation #238

Merged
merged 10 commits into from
Nov 26, 2024
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