Browse Source

docs: updates README.md

e22m4u 2 weeks ago
parent
commit
6752c9128c
4 changed files with 346 additions and 2 deletions
  1. 100 0
      README.md
  2. 244 0
      dist/cjs/index.cjs
  3. 1 1
      src/data-mapping-schema.js
  4. 1 1
      src/index.js

+ 100 - 0
README.md

@@ -23,6 +23,106 @@ import {TrieRouterDataMapper} from '@e22m4u/js-trie-router-data-mapper';
 const {TrieRouterDataMapper} = require('@e22m4u/js-trie-router-data-mapper');
 ```
 
+## Использование
+
+Подключение модуля к маршрутизатору.
+
+```js
+import {TrieRouter} from '@e22m4u/js-trie-router';
+import {TrieRouterDataMapper} from '@e22m4u/js-trie-router-data-mapper';
+
+const router = new TrieRouter();
+router.useService(TrieRouterDataMapper);
+```
+
+Пример парсинга Query-параметра.
+
+```js
+router.defineRoute({
+  method: HttpMethod.GET,
+  path: '/parseQuery',
+  meta: {
+    dataMap: { // определение карты данных
+      filter: { // свойство "filter" будет добавлено в "state"
+        source: HttpData.REQUEST_QUERY, // откуда брать данные
+        property: 'filter',       // извлечь свойство (опционально)
+        schema: {                 // схема данных для парсинга и проверки
+          type: DataType.OBJECT,  // разобрать значение как объект
+          required: true,         // в противном случае выбросить ошибку
+        },
+        // подробнее о схеме данных (параметр "schema")
+        // см. модуль @e22m4u/js-data-schema
+      },
+    },
+  },
+  handler: ({state: {filter}}) => {
+    // для запроса GET /parseQuery?filter={"foo":"bar"}
+    // значение параметра "filter" будет следующим:
+    console.log(typeof filter); // "object"
+    console.log(filter);        // {foo: 'bar'}
+    // если значение разобрать не удалось,
+    // то будет выброшена ошибка 
+    return filter;
+  },
+});
+```
+
+Список источников данных.
+
+```js
+export const HttpData = {
+  REQUEST_PARAMS: 'requestParams',
+  REQUEST_QUERY: 'requestQuery',
+  REQUEST_HEADERS: 'requestHeaders',
+  REQUEST_COOKIES: 'requestCookies',
+  REQUEST_BODY: 'requestBody',
+  RESPONSE_BODY: 'responseBody',
+};
+```
+
+Описание параметров `dataMap` (метаданные маршрута).
+
+- `source: HttpData` источник данных;
+- `property?: string` извлечение указанного свойства;
+- `schema?: DataSchema` схема данных для парсинга и проверки;
+- `projection?: DataProjection` схема проекции данных;
+
+Пример проекции ответа.
+
+```js
+router.defineRoute({
+  method: HttpMethod.GET,
+  path: '/responseProjection',
+  meta: {
+    dataMap: { // определение карты данных
+      response: {
+        // свойство "response" не будет добавлено в "state",
+        // так как в данном случае источником выступает
+        // ответ маршрута
+        source: HttpData.RESPONSE_BODY, // источник данных
+        projection: {foo: true, bar: false}, // схема проекции
+        // подробнее о схеме проекции (параметр "projection")
+        // см. модуль @e22m4u/js-data-projector
+      },
+    },
+  },
+  handler: () => {
+    return {
+      foo: 10, // доступно, явное правило
+      bar: 20, // исключено, явное правило
+      baz: 30, // исключено, отсутствует в схеме проекции
+    };
+  },
+});
+// для запроса GET /responseProjection
+// ответ будет {"foo":10}
+```
+
+Структура схемы данных и схемы проекции.
+
+- Схема данных см. описание [@e22m4u/js-data-schema](https://www.npmjs.com/package/@e22m4u/js-data-schema)
+- Схема проекции см. описание [@e22m4u/js-data-projector](https://www.npmjs.com/package/@e22m4u/js-data-projector)
+
 ## Тесты
 
 ```bash

+ 244 - 0
dist/cjs/index.cjs

@@ -0,0 +1,244 @@
+"use strict";
+var __defProp = Object.defineProperty;
+var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
+var __getOwnPropNames = Object.getOwnPropertyNames;
+var __hasOwnProp = Object.prototype.hasOwnProperty;
+var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
+var __export = (target, all) => {
+  for (var name in all)
+    __defProp(target, name, { get: all[name], enumerable: true });
+};
+var __copyProps = (to, from, except, desc) => {
+  if (from && typeof from === "object" || typeof from === "function") {
+    for (let key of __getOwnPropNames(from))
+      if (!__hasOwnProp.call(to, key) && key !== except)
+        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
+  }
+  return to;
+};
+var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
+
+// src/index.js
+var index_exports = {};
+__export(index_exports, {
+  DataSchemaRegistry: () => import_js_data_schema2.DataSchemaRegistry,
+  DataType: () => import_js_data_schema2.DataType,
+  HTTP_DATA_LIST: () => HTTP_DATA_LIST,
+  HttpData: () => HttpData,
+  ProjectionSchemaRegistry: () => import_js_data_projector2.ProjectionSchemaRegistry,
+  TrieRouterDataMapper: () => TrieRouterDataMapper,
+  validateDataMappingSchema: () => validateDataMappingSchema
+});
+module.exports = __toCommonJS(index_exports);
+
+// src/data-mapping-schema.js
+var HttpData = {
+  REQUEST_PARAMS: "requestParams",
+  REQUEST_QUERY: "requestQuery",
+  REQUEST_HEADERS: "requestHeaders",
+  REQUEST_COOKIES: "requestCookies",
+  REQUEST_BODY: "requestBody",
+  RESPONSE_BODY: "responseBody"
+};
+var HTTP_DATA_LIST = Object.values(HttpData);
+
+// src/trie-router-data-mapper.js
+var import_js_service = require("@e22m4u/js-service");
+var import_js_format2 = require("@e22m4u/js-format");
+var import_js_data_projector = require("@e22m4u/js-data-projector");
+var import_js_trie_router = require("@e22m4u/js-trie-router");
+var import_js_data_schema = require("@e22m4u/js-data-schema");
+
+// src/validate-data-mapping-schema.js
+var import_js_format = require("@e22m4u/js-format");
+function validateDataMappingSchema(schema) {
+  if (!schema || typeof schema !== "object" || Array.isArray(schema)) {
+    throw new import_js_format.InvalidArgumentError(
+      "Mapping schema must be an Object, but %v was given.",
+      schema
+    );
+  }
+  Object.keys(schema).forEach((propName) => {
+    const propOptions = schema[propName];
+    if (propOptions === void 0) {
+      return;
+    }
+    if (!propOptions || typeof propOptions !== "object" || Array.isArray(propOptions)) {
+      throw new import_js_format.InvalidArgumentError(
+        "Property options must be an Object, but %v was given.",
+        propOptions
+      );
+    }
+    if (!propOptions.source || typeof propOptions.source !== "string" || !HTTP_DATA_LIST.includes(propOptions.source)) {
+      throw new import_js_format.InvalidArgumentError(
+        "Data source %v is not supported.",
+        propOptions.source
+      );
+    }
+    if (propOptions.property !== void 0 && (!propOptions.property || typeof propOptions.property !== "string")) {
+      throw new import_js_format.InvalidArgumentError(
+        "Property name must be a non-empty String, but %v was given.",
+        propOptions.property
+      );
+    }
+  });
+}
+__name(validateDataMappingSchema, "validateDataMappingSchema");
+
+// src/trie-router-data-mapper.js
+var HTTP_DATA_TO_CONTEXT_PROPERTY_MAP = {
+  [HttpData.REQUEST_PARAMS]: "params",
+  [HttpData.REQUEST_QUERY]: "query",
+  [HttpData.REQUEST_HEADERS]: "headers",
+  [HttpData.REQUEST_COOKIES]: "cookies",
+  [HttpData.REQUEST_BODY]: "body"
+};
+var _TrieRouterDataMapper = class _TrieRouterDataMapper extends import_js_service.Service {
+  /**
+   * Constructor.
+   *
+   * @param {import('@e22m4u/js-service').ServiceContainer} container
+   */
+  constructor(container) {
+    super(container);
+    const router = this.getService(import_js_trie_router.TrieRouter);
+    if (!router.hasPreHandler(dataMappingPreHandler)) {
+      router.addPreHandler(dataMappingPreHandler);
+    }
+    if (!router.hasPostHandler(dataMappingPostHandler)) {
+      router.addPostHandler(dataMappingPostHandler);
+    }
+  }
+  /**
+   * Create state by mapping schema.
+   *
+   * @param {import('@e22m4u/js-trie-router').RequestContext} ctx
+   * @param {import('./data-mapping-schema.js').DataMappingSchema} schema
+   * @returns {object}
+   */
+  createStateByMappingSchema(ctx, schema) {
+    if (!(ctx instanceof import_js_trie_router.RequestContext)) {
+      throw new import_js_format2.InvalidArgumentError(
+        'Parameter "ctx" must be a RequestContext instance, but %v was given.',
+        ctx
+      );
+    }
+    validateDataMappingSchema(schema);
+    const res = {};
+    const dataParser = this.getService(import_js_data_schema.DataParser);
+    const dataProjector = this.getService(import_js_data_projector.DataProjector);
+    Object.keys(schema).forEach((propName) => {
+      const propOptions = schema[propName];
+      if (propOptions === void 0) {
+        return;
+      }
+      const ctxProp = HTTP_DATA_TO_CONTEXT_PROPERTY_MAP[propOptions.source];
+      if (ctxProp === void 0) {
+        return;
+      }
+      let value = ctx[ctxProp];
+      if (propOptions.property && typeof propOptions.property === "string") {
+        if (value && typeof value === "object" && !Array.isArray(value)) {
+          value = value[propOptions.property];
+        } else {
+          throw new import_js_format2.InvalidArgumentError(
+            "Property %v does not exist in %v value from the property %v of the request context.",
+            propOptions.property,
+            value,
+            ctxProp
+          );
+        }
+      }
+      if (propOptions.schema !== void 0) {
+        const sourcePath = propOptions.property ? `request.${ctxProp}.${propOptions.property}` : `request.${ctxProp}`;
+        if (import_js_data_schema.DATA_TYPE_LIST.includes(propOptions.schema)) {
+          const dataSchema = { type: propOptions.schema };
+          value = dataParser.parse(value, dataSchema, { sourcePath });
+        } else {
+          value = dataParser.parse(value, propOptions.schema, { sourcePath });
+        }
+      }
+      if (propOptions.projection !== void 0) {
+        value = dataProjector.projectInput(value, propOptions.projection);
+      }
+      res[propName] = value;
+    });
+    return res;
+  }
+  /**
+   * Filter response by mapping schema.
+   *
+   * @param {*} data
+   * @param {import('./data-mapping-schema.js').DataMappingSchema} schema
+   * @returns {*}
+   */
+  filterResponseByMappingSchema(data, schema) {
+    validateDataMappingSchema(schema);
+    let res = data;
+    const dataParser = this.getService(import_js_data_schema.DataParser);
+    const dataProjector = this.getService(import_js_data_projector.DataProjector);
+    Object.keys(schema).forEach((propName) => {
+      const propOptions = schema[propName];
+      if (propOptions === void 0) {
+        return;
+      }
+      if (propOptions.source !== HttpData.RESPONSE_BODY) {
+        return;
+      }
+      if (propOptions.property !== void 0) {
+        throw new import_js_format2.InvalidArgumentError(
+          'Option "property" is not supported for the %v source, but %v was given.',
+          propOptions.property
+        );
+      }
+      if (propOptions.schema !== void 0) {
+        const sourcePath = "response.body";
+        if (import_js_data_schema.DATA_TYPE_LIST.includes(propOptions.schema)) {
+          const dataSchema = { type: propOptions.schema };
+          res = dataParser.parse(res, dataSchema, { sourcePath });
+        } else {
+          res = dataParser.parse(res, propOptions.schema, { sourcePath });
+        }
+      }
+      if (propOptions.projection !== void 0) {
+        res = dataProjector.projectOutput(res, propOptions.projection);
+      }
+    });
+    return res;
+  }
+};
+__name(_TrieRouterDataMapper, "TrieRouterDataMapper");
+var TrieRouterDataMapper = _TrieRouterDataMapper;
+function dataMappingPreHandler(ctx) {
+  const schema = (ctx.meta || {}).dataMap;
+  if (schema === void 0) {
+    return;
+  }
+  const mapper = ctx.container.get(TrieRouterDataMapper);
+  const state = mapper.createStateByMappingSchema(ctx, schema);
+  ctx.state = { ...ctx.state, ...state };
+}
+__name(dataMappingPreHandler, "dataMappingPreHandler");
+function dataMappingPostHandler(ctx, data) {
+  const schema = (ctx.meta || {}).dataMap;
+  if (schema === void 0) {
+    return;
+  }
+  const mapper = ctx.container.get(TrieRouterDataMapper);
+  return mapper.filterResponseByMappingSchema(data, schema);
+}
+__name(dataMappingPostHandler, "dataMappingPostHandler");
+
+// src/index.js
+var import_js_data_projector2 = require("@e22m4u/js-data-projector");
+var import_js_data_schema2 = require("@e22m4u/js-data-schema");
+// Annotate the CommonJS export names for ESM import in node:
+0 && (module.exports = {
+  DataSchemaRegistry,
+  DataType,
+  HTTP_DATA_LIST,
+  HttpData,
+  ProjectionSchemaRegistry,
+  TrieRouterDataMapper,
+  validateDataMappingSchema
+});

+ 1 - 1
src/data-mapping-schema.js

@@ -13,4 +13,4 @@ export const HttpData = {
 /**
  * Http data list.
  */
-export const HTTP_DATA_LIST = Object.values(HttpData);
+export const HTTP_DATA_LIST = Object.values(HttpData);

+ 1 - 1
src/index.js

@@ -2,4 +2,4 @@ export * from './data-mapping-schema.js';
 export * from './trie-router-data-mapper.js';
 export * from './validate-data-mapping-schema.js';
 export {ProjectionSchemaRegistry} from '@e22m4u/js-data-projector';
-export {DataType, DataSchemaRegistry} from '@e22m4u/js-data-schema';
+export {DataType, DataSchemaRegistry} from '@e22m4u/js-data-schema';