Skip to content

Commit

Permalink
Merge pull request #8 from ty-ras/issue/3-add-jsdoc
Browse files Browse the repository at this point in the history
Issue/3 add jsdoc
  • Loading branch information
stazz authored May 27, 2023
2 parents fcb1c57 + 0af7ee1 commit de62a64
Show file tree
Hide file tree
Showing 31 changed files with 2,054 additions and 1,422 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,9 @@ jobs:
matrix:
dir:
- data
# - data-backend
# - data-frontend
# - metadata-jsonschema
- data-backend
- data-frontend
- metadata-jsonschema
runs-on: ubuntu-latest
name: Build and test ${{ matrix.dir }}
steps:
Expand Down
44 changes: 25 additions & 19 deletions data-backend/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@ty-ras/data-backend-zod",
"version": "0.13.0",
"version": "1.0.0",
"author": {
"name": "Stanislav Muhametsin",
"email": "[email protected]",
Expand Down Expand Up @@ -31,38 +31,44 @@
}
},
"dependencies": {
"@ty-ras/data-zod": "0.13.0",
"@ty-ras/endpoint-spec": "0.13.0"
"@ty-ras/data-zod": "^1.0.0",
"@ty-ras/endpoint-spec": "^1.0.0"
},
"peerDependencies": {
"raw-body": "^2.5.1"
},
"devDependencies": {
"@babel/core": "7.19.3",
"@babel/eslint-parser": "7.19.1",
"@types/node": "18.7.18",
"@typescript-eslint/eslint-plugin": "5.38.0",
"@typescript-eslint/parser": "5.38.0",
"ava": "5.0.1",
"c8": "7.12.0",
"eslint": "8.23.1",
"eslint-config-prettier": "8.5.0",
"@babel/core": "7.21.5",
"@babel/eslint-parser": "7.21.3",
"@typescript-eslint/eslint-plugin": "5.59.2",
"@typescript-eslint/parser": "5.59.2",
"@types/node": "18.16.3",
"ava": "5.2.0",
"c8": "7.13.0",
"eslint": "8.39.0",
"eslint-plugin-jsdoc": "43.1.1",
"eslint-plugin-path-import-extension": "0.9.0",
"eslint-plugin-type-only-import": "0.9.0",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-sonarjs": "0.15.0",
"prettier": "2.7.1",
"eslint-plugin-sonarjs": "0.19.0",
"prettier": "2.8.8",
"raw-body": "2.5.1",
"ts-node": "10.9.1",
"typescript": "4.8.4",
"typescript": "5.0.4",
"zod": "3.20.6"
},
"scripts": {
"build:run": "yarn run lint && yarn run tsc",
"build:ci": "yarn run clear-build-artifacts && yarn run compile-d-ts-files && yarn run tsc --outDir ./dist-esm && yarn run tsc --module CommonJS --outDir ./dist-cjs && yarn run format-output-files",
"build:ci": "yarn run clear-build-artifacts && yarn run compile-d-ts-files && yarn run tsc --outDir ./dist-esm && yarn run tsc --module CommonJS --outDir ./dist-cjs && yarn run remove-empty-js-files && yarn run generate-stub-package-json-for-cjs && yarn run format-output-files",
"clear-build-artifacts": "rm -rf dist dist-ts dist-cjs dist-esm build",
"compile-d-ts-files": "yarn run tsc --removeComments false --emitDeclarationOnly --declaration --declarationDir ./dist-ts && yarn run copy-d-ts-files && yarn run tsc:plain --project tsconfig.out.json",
"copy-d-ts-files": "find ./src -mindepth 1 -maxdepth 1 -name '*.d.ts' -exec cp {} ./dist-ts +",
"format-output-files": "find dist-ts -name '*.ts' -type f -exec sh -c \"echo '/* eslint-disable */\n/* eslint-enable prettier/prettier */'\"' | cat - $1 > $1.tmp && mv $1.tmp $1' -- {} \\; && eslint --no-eslintrc --config '.eslintrc.output.ts.cjs' --fix './dist-ts/**/*.ts' && eslint --no-eslintrc --config '.eslintrc.output.cjs' --fix 'dist-cjs/*js' 'dist-esm/*js'",
"compile-d-ts-files": "yarn run tsc --removeComments false --emitDeclarationOnly --declaration --declarationDir ./dist-ts && yarn run tsc:plain --project tsconfig.out.json",
"format-output-files": "yarn run format-output-files-ts && yarn run format-output-files-js",
"format-output-files-ts": "eslint --no-eslintrc --config '.eslintrc.out-ts.cjs' --fix --fix-type layout './dist-ts/**/*.ts'",
"format-output-files-js": "eslint --no-eslintrc --config '.eslintrc.out.cjs' --fix 'dist-cjs/**/*js' 'dist-esm/**/*js'",
"generate-stub-package-json-for-cjs": "../scripts/generate-stub-package-json.cjs",
"lint": "eslint ./src --ext .ts,.tsx",
"remove-empty-js-files": "../scripts/remove-empty-js-files.cjs",
"tsc": "tsc --project tsconfig.build.json",
"tsc:plain": "tsc",
"test:coverage": "c8 --temp-directory /tmp ava",
Expand Down
38 changes: 35 additions & 3 deletions data-backend/src/__test__/body.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/**
* @file This file contains unit tests for functionality in file `../body.ts`.
*/

/* eslint-disable @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */

import test from "ava";
import * as spec from "../body";
import * as t from "zod";
Expand Down Expand Up @@ -141,3 +144,32 @@ test("Validate request body detects invalid JSON", async (c) => {
c.true(result.errorInfo instanceof SyntaxError);
}
});

test("Validate that content type is customizable for request and response body validations", (c) => {
c.plan(2);

const validator = t.string();

// Technically, if the code wouldn't work, these would not even pass TS type checking.
const {
validatorSpec: {
contents: { customContent },
},
} = spec.requestBody(validator, { contentType: "customContent" });
c.is(
customContent,
validator,
"The content type must've propagated when passed to the function",
);

const {
validatorSpec: {
contents: { customContentResponse },
},
} = spec.responseBody(validator, "customContentResponse");
c.is(
customContentResponse,
validator,
"The content type must've propagated when passed to the function",
);
});
12 changes: 8 additions & 4 deletions data-backend/src/__test__/headers.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/**
* @file This file contains unit tests for functionality in file `../headers.ts`.
*/

/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */

import test from "ava";
import * as spec from "../headers";
import * as t from "zod";

test("Validate headers works", (c) => {
c.plan(5);
const headerParamValue = t.string();
const { validators, metadata } = spec.headers({
const { validators, metadata } = spec.requestHeaders({
headerParam: headerParamValue,
});
c.deepEqual(metadata, {
Expand Down Expand Up @@ -56,7 +60,7 @@ test("Validate string decoding optionality detection", (c) => {
c.plan(3);
const headerType = t.string();
const optionalHeaderType = t.union([headerType, t.undefined()]);
const { validators, metadata } = spec.headers({
const { validators, metadata } = spec.requestHeaders({
requiredHeader: headerType,
optionalHeader: optionalHeaderType,
});
Expand Down
7 changes: 5 additions & 2 deletions data-backend/src/__test__/url.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable @typescript-eslint/no-unsafe-argument */
/**
* @file This file contains unit tests for functionality in file `../url.ts`.
*/

/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-argument */
import test from "ava";
import * as spec from "../url";
import * as t from "zod";
Expand Down
148 changes: 123 additions & 25 deletions data-backend/src/body.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,145 @@
import * as dataBE from "@ty-ras/data-backend";
/**
* @file This file contains code to create generic TyRAS callbacks from 'native' `zod` validators, for HTTP request and response body validation.
*/

import * as data from "@ty-ras/data-backend";
import * as common from "@ty-ras/data-zod";
import * as rawbody from "raw-body";
import type * as protocol from "./protocol.types";

// We only support json things for zod validation.
/**
* This is the default MIME type used to serialize/deserialize response/request bodies.
* Its value is `application/json`.
*/
export const CONTENT_TYPE = "application/json" as const;

export const requestBody = <T>(
/**
* Creates a new generic TyRAS {@link data.DataValidatorRequestInputSpec} for validating request body, wrapping native `zod` {@link common.Decoder}, without additional options.
* @param validation The {@link common.Decoder} responsible for validating the deserialized and and JSON-parsed request body.
* @returns The {@link data.DataValidatorRequestInputSpec} that can be passed to TyRAS functions as request body validator.
*/
export function requestBody<T>(
validation: common.Decoder<T>,
): data.DataValidatorRequestInputSpec<
T,
protocol.InputValidatorSpec<T, typeof CONTENT_TYPE>
>;

/**
* Creates a new generic TyRAS {@link data.DataValidatorRequestInputSpec} for validating request body, wrapping native `zod` {@link common.Decoder}, with additional options.
* @param validation The {@link common.Decoder} responsible for validating the deserialized and and JSON-parsed request body.
* @param opts The {@link RequestBodyCreationOptions}, without custom content type specifier.
* @returns The {@link data.DataValidatorRequestInputSpec} that can be passed to TyRAS functions as request body validator.
*/
export function requestBody<T>(
validation: common.Decoder<T>,
opts: RequestBodyCreationOptions & { contentType?: never },
): data.DataValidatorRequestInputSpec<
T,
protocol.InputValidatorSpec<T, typeof CONTENT_TYPE>
>;

/**
* Creates a new generic TyRAS {@link data.DataValidatorRequestInputSpec} for validating request body, wrapping native `zod` {@link common.Decoder}, with additional options, including content type.
* @param validation The {@link common.Decoder} responsible for validating the deserialized and and JSON-parsed request body.
* @param opts The {@link RequestBodyCreationOptions}, along with custom content type specifier.
* @returns The {@link data.DataValidatorRequestInputSpec} that can be passed to TyRAS functions as request body validator.
*/
export function requestBody<T, TContentType extends string>(
validation: common.Decoder<T>,
opts: RequestBodyCreationOptions & { contentType: TContentType },
): data.DataValidatorRequestInputSpec<
T,
protocol.InputValidatorSpec<T, TContentType>
>;

/**
* Creates a new generic TyRAS {@link data.DataValidatorRequestInputSpec} for validating request body, wrapping native `zod` {@link common.Decoder}, with additional options, possibly including content type.
* @param validation The {@link common.Decoder} responsible for validating the deserialized and and JSON-parsed request body.
* @param opts The {@link RequestBodyCreationOptions}, possibly with custom content type specifier.
* @returns The {@link data.DataValidatorRequestInputSpec} that can be passed to TyRAS functions as request body validator.
*/
export function requestBody<T, TContentType extends string>(
validation: common.Decoder<T>,
strictContentType = false,
opts?: rawbody.Options,
): dataBE.DataValidatorRequestInputSpec<T, InputValidatorSpec<T>> =>
dataBE.requestBodyGeneric(
opts?: RequestBodyCreationOptions & { contentType?: TContentType },
): data.DataValidatorRequestInputSpec<
T,
protocol.InputValidatorSpec<T, TContentType>
> {
return data.requestBodyGeneric(
validation,
common.plainValidator(validation),
CONTENT_TYPE,
strictContentType,
common.fromDecoder(validation),
opts?.contentType ?? CONTENT_TYPE,
opts?.strictContentType === true,
async (readable, encoding) => {
const bufferOrString = await rawbody.default(readable, {
encoding: opts?.encoding ?? encoding,
...(opts ?? {}),
encoding: opts?.opts?.encoding ?? encoding,
...(opts?.opts ?? {}),
});
return bufferOrString instanceof Buffer
? bufferOrString.toString()
: bufferOrString;
},
);
}

/**
* Creates a new generic TyRAS {@link data.DataValidatorResponseOutputSpec} for validating response body, wrapping native `zod` {@link common.Encoder}.
* @param validation The {@link common.Encoder} responsible for validating the response body before it is stringified and serialized.
* @returns The {@link data.DataValidatorResponseOutputSpec} that can be passed to TyRAS functions as response body validator.
*/
export function responseBody<TOutput, TSerialized>(
validation: common.Encoder<TOutput, TSerialized>,
): data.DataValidatorResponseOutputSpec<
TOutput,
protocol.OutputValidatorSpec<TOutput, TSerialized, typeof CONTENT_TYPE>
>;

/**
* Creates a new generic TyRAS {@link data.DataValidatorResponseOutputSpec} for validating response body, wrapping native `zod` {@link common.Encoder}.
* @param validation The {@link common.Encoder} responsible for validating the response body before it is stringified and serialized.
* @param contentType The content type to use.
* @returns The {@link data.DataValidatorResponseOutputSpec} that can be passed to TyRAS functions as response body validator.
*/
export function responseBody<TOutput, TSerialized, TContentType extends string>(
validation: common.Encoder<TOutput, TSerialized>,
contentType: TContentType,
): data.DataValidatorResponseOutputSpec<
TOutput,
protocol.OutputValidatorSpec<TOutput, TSerialized, TContentType>
>;

export const responseBody = <TOutput, TSerialized>(
/**
* Creates a new generic TyRAS {@link data.DataValidatorResponseOutputSpec} for validating response body, wrapping native `zod` {@link common.Encoder}.
* @param validation The {@link common.Encoder} responsible for validating the response body before it is stringified and serialized.
* @param contentType The possible content type to use.
* @returns The {@link data.DataValidatorResponseOutputSpec} that can be passed to TyRAS functions as response body validator.
*/
export function responseBody<TOutput, TSerialized, TContentType extends string>(
validation: common.Encoder<TOutput, TSerialized>,
): dataBE.DataValidatorResponseOutputSpec<
contentType?: TContentType,
): data.DataValidatorResponseOutputSpec<
TOutput,
OutputValidatorSpec<TOutput, TSerialized>
> =>
dataBE.responseBodyGeneric(
protocol.OutputValidatorSpec<TOutput, TSerialized, TContentType>
> {
return data.responseBodyGeneric(
validation,
common.plainValidatorEncoder(validation),
CONTENT_TYPE,
common.fromEncoder(validation),
contentType ?? CONTENT_TYPE,
);
}

export type InputValidatorSpec<TData> = {
[CONTENT_TYPE]: common.Decoder<TData>;
};
/**
* This interface contains data that can be specified when using {@link requestBody}.
*/
export interface RequestBodyCreationOptions {
/**
* If set to `true`, the returned request body validator will throw an error if request's `Content-Type` header does not match exactly the given content type (or {@link CONTENT_TYPE} if not given).
*/
strictContentType?: boolean;

export type OutputValidatorSpec<TOutput, TSerialized> = {
[CONTENT_TYPE]: common.Encoder<TOutput, TSerialized>;
};
/**
* The option {@link rawbody.Options} to use when deserializing the request body.
*/
opts?: rawbody.Options;
}
20 changes: 17 additions & 3 deletions data-backend/src/headers.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,30 @@
/**
* @file This file contains code to create generic TyRAS callbacks from 'native' `zod` validators, for HTTP request and response headers validation.
*/

import * as data from "@ty-ras/data";
import type * as dataBE from "@ty-ras/data-backend";
import * as common from "@ty-ras/data-zod";
import * as stringDecoder from "./string-decoder-generic";
import * as stringEncoder from "./string-encoder-generic";
import * as stringDecoder from "./string-decoder";
import * as stringEncoder from "./string-encoder";

export const headers = <TValidation extends stringDecoder.TDecoderBase>(
/**
* Creates a new generic TyRAS {@link dataBE.RequestHeaderDataValidatorSpec} for validating request headers, wrapping named native `zod` {@link common.Decoder}.
* @param validation The named {@link common.Decoder}s, each responsible for validating the request header value. The key is header name, and the value is the {@link common.Decoder}.
* @returns The {@link data.RequestHeaderDataValidatorSpec} that can be passed to TyRAS functions as response body validator.
*/
export const requestHeaders = <TValidation extends stringDecoder.TDecoderBase>(
validation: TValidation,
): dataBE.RequestHeaderDataValidatorSpec<
stringDecoder.GetDecoderData<TValidation>,
common.Decoder<unknown>
> => stringDecoder.stringDecoder(validation, "Header");

/**
* Creates a new generic TyRAS {@link dataBE.ResponseHeaderDataValidatorSpec} for validating response headers, wrapping named native `zod` {@link common.Encoder}.
* @param validation The named {@link common.Encoder}s, each responsible for validating the response header value. The key is header name, and the value is the {@link common.Encoder}.
* @returns The {@link data.ResponseHeaderDataValidatorSpec} that can be passed to TyRAS functions as response body validator.
*/
export const responseHeaders = <TValidation extends stringEncoder.TEncoderBase>(
validation: TValidation,
): dataBE.ResponseHeaderDataValidatorSpec<
Expand Down
Loading

0 comments on commit de62a64

Please sign in to comment.