Skip to content
106 changes: 97 additions & 9 deletions openapi3.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,12 @@ paths:
summary: file cache
value:
cacheName: example-layer
sources: []
grids:
- epsg4326dir
format: image/png
upscale_tiles: 18
minimize_meta_requests: true
cache:
type: file
directory: >-
Expand All @@ -250,16 +256,43 @@ paths:
summary: s3 cache
value:
cacheName: example-layer
sources: []
grids:
- epsg4326dir
format: image/png
upscale_tiles: 18
minimize_meta_requests: true
cache:
type: s3
directory: >-
/5eedfc75-861c-42fc-81a9-ab2c0b95c274/d8527f15-5377-4a28-b5ce-92891d897aec/
directory_layout: tms
bucket_name: bucket-name
geopackage:
summary: geopackage cache
value:
cacheName: example-layer
sources: []
grids:
- epsg4326dir
format: image/png
upscale_tiles: 18
minimize_meta_requests: true
cache:
type: geopackage
filename: /path/to/tiles/directory/example-layer.gpkg
table_name: example-layer
redis:
summary: redis cache
summary: >-
redis cache, resolved by the '-redis' suffix. It carries no
upscale_tiles and no minimize_meta_requests.
value:
cacheName: example-layer-redis
sources:
- example-layer
grids:
- epsg4326dir
format: image/png
cache:
host: mapproxy-redis-master
port: 6379
Expand Down Expand Up @@ -322,8 +355,11 @@ components:
- JPEG
getLayerResponse:
type: object
description: >-
A cache as written in the mapproxy configuration. `cache`, the cache source, is
absent on configuration entries that are not well formed caches, so it is not
required.
required:
- cache
- grids
- sources
- upscale_tiles
Expand Down Expand Up @@ -355,10 +391,12 @@ components:
- image/jpeg
fileCache:
type: object
description: >-
A file cache source. Only `type` is guaranteed; a cache source missing an
optional field is still a valid response.
required:
- type
- directory
- directory_layout
additionalProperties: true
properties:
type:
type: string
Expand All @@ -373,10 +411,12 @@ components:
example: tms
s3Cache:
type: object
description: >-
An s3 cache source. Only `type` is guaranteed; a cache source missing an
optional field is still a valid response.
required:
- type
- directory
- directory_layout
additionalProperties: true
properties:
type:
type: string
Expand All @@ -396,11 +436,12 @@ components:
example: bucket-name
redisCache:
type: object
description: >-
A redis cache source. Only `type` is guaranteed; a cache source missing an
optional field is still a valid response.
required:
- type
- host
- port
- default_ttl
additionalProperties: true
properties:
type:
type: string
Expand All @@ -423,25 +464,72 @@ components:
default_ttl:
type: integer
example: 86400
geopackageCache:
type: object
description: >-
A geopackage cache source. Only `type` is guaranteed; a cache source missing
an optional field is still a valid response.
required:
- type
additionalProperties: true
properties:
type:
type: string
enum:
- geopackage
filename:
type: string
example: /path/to/tiles/directory/amsterdam_5cm.gpkg
table_name:
type: string
example: amsterdam_5cm
getCacheResponse:
type: object
description: >-
The whole cache as written in the mapproxy configuration, alongside its name.
Only `cacheName` and `cache` are guaranteed: the cache is returned verbatim, so
mapproxy options this service does not model are present too, and options a cache
genuinely lacks are absent rather than null or defaulted.
required:
- cacheName
- cache
additionalProperties: true
properties:
cacheName:
type: string
description: >-
The resolved cache name, which for a redis request is the '-redis' suffixed
name rather than the requested layer name.
example: example-layer
sources:
type: array
items:
type: string
grids:
type: array
items:
type: string
format:
type: string
example: image/png
upscale_tiles:
type: number
example: 18
minimize_meta_requests:
type: boolean
cache:
oneOf:
- $ref: '#/components/schemas/fileCache'
- $ref: '#/components/schemas/s3Cache'
- $ref: '#/components/schemas/redisCache'
- $ref: '#/components/schemas/geopackageCache'
discriminator:
propertyName: type
mapping:
file: '#/components/schemas/fileCache'
s3: '#/components/schemas/s3Cache'
redis: '#/components/schemas/redisCache'
geopackage: '#/components/schemas/geopackageCache'
getConfigResponse:
type: object
properties:
Expand Down
19 changes: 13 additions & 6 deletions src/common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ export interface IRedisConfig {
export interface IMapProxyJsonDocument {
services: JsonObject;
layers: IMapProxyLayer[];
caches: IMapProxyCache;
caches: Record<string, IMapProxyCache>;
grids: JsonObject;
globals: IMapProxyGlobalConfig;
}
Expand Down Expand Up @@ -120,10 +120,13 @@ export interface ICacheName {
cacheName: string;
}

export interface ICacheObject {
cacheName: string;
cache: IRedisSource | IS3Source | IFSSource;
}
/**
* The response of GET /layer/{layerName}/{cacheType}: the Cache name, and the Cache spread
* verbatim over it. Only the Cache name and the Cache Source are declared, because they are
* all that is verified — every other key is whatever the configuration held, so a consumer
* must narrow rather than trust.
*/
export type IGetCacheResponse = ICacheName & Pick<IMapProxyCache, 'cache'> & Record<string, unknown>;

export interface IGpkgSource extends ICacheSource {
filename: string;
Expand All @@ -142,7 +145,11 @@ export interface IMapProxyCache {
grids: string[];
format: string;
upscale_tiles?: number;
cache: ICacheSource;
/**
* Absent on configuration entries that are not well formed Caches. Production configurations
* hold such entries, so every read must narrow rather than assume a Cache Source is there.
*/
cache?: ICacheSource;
minimize_meta_requests?: boolean;
}

Expand Down
4 changes: 2 additions & 2 deletions src/layers/controllers/layersController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ import type { RequestHandler } from 'express';
import httpStatus from 'http-status-codes';
import { injectable, inject } from 'tsyringe';
import { SERVICES } from '../../common/constants';
import type { ICacheName, ILayerPostRequest, IMapProxyCache } from '../../common/interfaces';
import type { IGetCacheResponse, ILayerPostRequest, IMapProxyCache } from '../../common/interfaces';
import { LayersManager } from '../models/layersManager';

type CreateLayerHandler = RequestHandler<undefined, ILayerPostRequest, ILayerPostRequest>;
type GetLayerHandler = RequestHandler<{ name: string }, IMapProxyCache, IMapProxyCache>;
type GetCacheHandler = RequestHandler<{ layerName: string; cacheType: string }, ICacheName>;
type GetCacheHandler = RequestHandler<{ layerName: string; cacheType: string }, IGetCacheResponse>;
type UpdateLayerHandler = RequestHandler<{ name: string }, ILayerPostRequest, ILayerPostRequest>;
type DeleteLayerHandler = RequestHandler<undefined, string[] | void, undefined, { layerNames: string[] }>;
@injectable()
Expand Down
30 changes: 15 additions & 15 deletions src/layers/models/layersManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,7 @@ import type {
ICacheProvider,
ICacheSource,
IRedisConfig,
ICacheObject,
IRedisSource,
IS3Source,
IFSSource,
IGetCacheResponse,
} from '../../common/interfaces';
import { isLayerNameExists } from '../../common/validations/isLayerNameExists';
import { S3Source } from '../../common/cacheProviders/S3Source';
Expand Down Expand Up @@ -54,7 +51,7 @@ class LayersManager {
}

@withSpanAsyncV4
public async getCacheByNameAndType(layerName: string, cacheType: string): Promise<ICacheObject> {
public async getCacheByNameAndType(layerName: string, cacheType: string): Promise<IGetCacheResponse> {
const configJson = await this.configProvider.getJson();
const requestedLayer = configJson.layers.find((layer) => layer.name === layerName);

Expand All @@ -66,25 +63,28 @@ class LayersManager {

// our current only real cache layer, other caches cases are known as the source layers
const cacheName = isSourceType(cacheType) && cacheType === SourceTypes.REDIS ? getRedisCacheName(layerName) : layerName;
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const currentSourceCache: IMapProxyCache | undefined = configJson.caches[cacheName];
const requestedCache: IMapProxyCache | undefined = configJson.caches[cacheName];

if (currentSourceCache === undefined) {
if (requestedCache === undefined) {
const errorMsg = `cache not found for ${layerName} layer`;
this.logger.warn({ msg: errorMsg, layerName, cacheType });
throw new NotFoundError(errorMsg);
}
if (currentSourceCache.cache.type !== cacheType) {

// Nothing about the request is malformed: the Layer and the Cache Type are both well formed
// and the Cache Type is in the enum. There is simply no Cache of that Cache Type to address.
const foundCacheType = requestedCache.cache?.type;

if (foundCacheType !== cacheType) {
const errorMsg = `${layerName} layer cache not found with requested cache type: ${cacheType}`;
this.logger.warn({ msg: errorMsg, layerName, cacheType });
throw new BadRequestError(errorMsg);
// The Cache itself is never logged: a redis Cache Source carries credentials.
this.logger.warn({ msg: errorMsg, layerName, cacheType, cacheName, foundCacheType });
throw new NotFoundError(errorMsg);
}

type AvailableSources = IRedisSource | IS3Source | IFSSource;

return {
cacheName: cacheName,
cache: currentSourceCache.cache as AvailableSources,
...requestedCache,
Comment thread
almog8k marked this conversation as resolved.
cacheName,
};
}

Expand Down
Loading
Loading