@flink-app/flink 0.13.3 → 0.13.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.__schemas = exports.__params = exports.__query = exports.__file = exports.__assumedHttpMethod = exports.Route = void 0;
40
+ var flink_1 = require("@flink-app/flink");
41
+ exports.Route = {
42
+ path: "/product/:productId",
43
+ method: flink_1.HttpMethod.patch,
44
+ };
45
+ var PatchProductWithIntersection = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
46
+ var req = _b.req;
47
+ return __generator(this, function (_c) {
48
+ return [2 /*return*/, {
49
+ data: {
50
+ name: "Test Product",
51
+ description: "Test Description",
52
+ price: req.body.update || 99.99,
53
+ },
54
+ }];
55
+ });
56
+ }); };
57
+ exports.default = PatchProductWithIntersection;
58
+ exports.__assumedHttpMethod = "patch", exports.__file = "PatchProductWithIntersection.ts", exports.__query = [], exports.__params = [{ description: "", name: "productId" }];
59
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "update": { "type": "object", "additionalProperties": false }, "fullUpdate": { "type": "object", "additionalProperties": false, "properties": { "notes": { "type": "string" }, "stock": { "type": "number" }, "warehouse": { "type": "string" }, "sku": { "type": "string" }, "name": { "type": "string" }, "description": { "type": "string" }, "price": { "type": "number" } }, "required": ["notes"] }, "metadataUpdate": { "type": "object", "additionalProperties": false, "properties": { "updatedBy": { "type": "string" } }, "required": ["updatedBy"] } }, "additionalProperties": false, "definitions": {} }, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "name": { "type": "string" }, "description": { "type": "string" }, "price": { "type": "number" } }, "required": ["name", "description", "price"], "additionalProperties": false, "definitions": {} } };
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
3
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
4
+ return new (P || (P = Promise))(function (resolve, reject) {
5
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
6
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
7
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
8
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
9
+ });
10
+ };
11
+ var __generator = (this && this.__generator) || function (thisArg, body) {
12
+ var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
13
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
14
+ function verb(n) { return function (v) { return step([n, v]); }; }
15
+ function step(op) {
16
+ if (f) throw new TypeError("Generator is already executing.");
17
+ while (g && (g = 0, op[0] && (_ = 0)), _) try {
18
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
19
+ if (y = 0, t) op = [op[0] & 2, t.value];
20
+ switch (op[0]) {
21
+ case 0: case 1: t = op; break;
22
+ case 4: _.label++; return { value: op[1], done: false };
23
+ case 5: _.label++; y = op[1]; op = [0]; continue;
24
+ case 7: op = _.ops.pop(); _.trys.pop(); continue;
25
+ default:
26
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
27
+ if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
28
+ if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
29
+ if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
30
+ if (t[2]) _.ops.pop();
31
+ _.trys.pop(); continue;
32
+ }
33
+ op = body.call(thisArg, _);
34
+ } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
35
+ if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
36
+ }
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.__schemas = exports.__params = exports.__query = exports.__file = exports.__assumedHttpMethod = exports.Route = void 0;
40
+ var flink_1 = require("@flink-app/flink");
41
+ exports.Route = {
42
+ path: "/user/:userId",
43
+ method: flink_1.HttpMethod.patch,
44
+ };
45
+ var PatchUserWithUnion = function (_a) { return __awaiter(void 0, [_a], void 0, function (_b) {
46
+ var req = _b.req;
47
+ return __generator(this, function (_c) {
48
+ return [2 /*return*/, {
49
+ data: {
50
+ firstName: "Test",
51
+ lastName: "User",
52
+ bio: req.body.data || "Default bio",
53
+ },
54
+ }];
55
+ });
56
+ }); };
57
+ exports.default = PatchUserWithUnion;
58
+ exports.__assumedHttpMethod = "patch", exports.__file = "PatchUserWithUnion.ts", exports.__query = [], exports.__params = [{ description: "", name: "userId" }];
59
+ exports.__schemas = { reqSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "data": { "type": "object", "additionalProperties": false }, "profileUpdate": { "anyOf": [{ "type": "object", "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "bio": { "type": "string" } }, "additionalProperties": false }, { "type": "object", "properties": { "theme": { "type": "string" }, "notifications": { "type": "boolean" }, "language": { "type": "string" } }, "additionalProperties": false }] } }, "additionalProperties": false, "definitions": {} }, resSchema: { "$schema": "http://json-schema.org/draft-07/schema#", "type": "object", "properties": { "firstName": { "type": "string" }, "lastName": { "type": "string" }, "bio": { "type": "string" } }, "required": ["firstName", "lastName", "bio"], "additionalProperties": false, "definitions": {} } };
@@ -0,0 +1,25 @@
1
+ import { Handler, HttpMethod, RouteProps } from "@flink-app/flink";
2
+
3
+ export const Route: RouteProps = {
4
+ path: "/car/:id",
5
+ method: HttpMethod.patch,
6
+ };
7
+
8
+ interface Car {
9
+ model: string;
10
+ year: number;
11
+ }
12
+
13
+ type Params = { id: string };
14
+ type PatchCarReq = Partial<Car>;
15
+
16
+ const PatchCar: Handler<any, PatchCarReq, Car, Params> = async ({ req }) => {
17
+ return {
18
+ data: {
19
+ model: req.body.model || "Updated Model",
20
+ year: req.body.year || 2024,
21
+ },
22
+ };
23
+ };
24
+
25
+ export default PatchCar;
@@ -0,0 +1,66 @@
1
+ import { Handler, HttpMethod, RouteProps } from "@flink-app/flink";
2
+
3
+ export const Route: RouteProps = {
4
+ path: "/onboarding/:sessionId",
5
+ method: HttpMethod.patch,
6
+ };
7
+
8
+ // Simulating a complex nested structure like in your example
9
+ interface OnboardingSession {
10
+ sessionId: string;
11
+ status: string;
12
+ extractedData: {
13
+ companyName: string;
14
+ orgNumber: string;
15
+ address: {
16
+ street: string;
17
+ city: string;
18
+ postalCode: string;
19
+ };
20
+ contactInfo: {
21
+ email: string;
22
+ phone: string;
23
+ };
24
+ };
25
+ metadata: {
26
+ createdAt: Date;
27
+ updatedAt: Date;
28
+ };
29
+ }
30
+
31
+ // Test case: Partial of a nested property using bracket notation
32
+ interface PatchOnboardingSessionReq {
33
+ status?: string;
34
+ extractedData?: Partial<OnboardingSession["extractedData"]>;
35
+ metadata?: Partial<OnboardingSession["metadata"]>;
36
+ }
37
+
38
+ type Params = { sessionId: string };
39
+
40
+ const PatchOnboardingSession: Handler<any, PatchOnboardingSessionReq, OnboardingSession, Params> = async ({ req }) => {
41
+ return {
42
+ data: {
43
+ sessionId: req.params.sessionId,
44
+ status: req.body.status || "pending",
45
+ extractedData: {
46
+ companyName: req.body.extractedData?.companyName || "Test Company",
47
+ orgNumber: req.body.extractedData?.orgNumber || "123456",
48
+ address: {
49
+ street: "Main St",
50
+ city: "Stockholm",
51
+ postalCode: "12345",
52
+ },
53
+ contactInfo: {
54
+ email: "test@example.com",
55
+ phone: "+46701234567",
56
+ },
57
+ },
58
+ metadata: {
59
+ createdAt: new Date(),
60
+ updatedAt: new Date(),
61
+ },
62
+ },
63
+ };
64
+ };
65
+
66
+ export default PatchOnboardingSession;
@@ -0,0 +1,79 @@
1
+ import { Handler, HttpMethod, RouteProps } from "@flink-app/flink";
2
+
3
+ export const Route: RouteProps = {
4
+ path: "/order/:orderId",
5
+ method: HttpMethod.patch,
6
+ };
7
+
8
+ // Complex nested structures for testing edge cases
9
+ interface OrderCustomer {
10
+ customerId: string;
11
+ name: string;
12
+ email: string;
13
+ address: {
14
+ street: string;
15
+ city: string;
16
+ country: string;
17
+ };
18
+ }
19
+
20
+ interface OrderItem {
21
+ productId: string;
22
+ quantity: number;
23
+ price: number;
24
+ }
25
+
26
+ interface OrderItems {
27
+ items: Array<OrderItem>;
28
+ subtotal: number;
29
+ tax: number;
30
+ }
31
+
32
+ interface OrderShipping {
33
+ method: string;
34
+ trackingNumber: string;
35
+ estimatedDelivery: Date;
36
+ }
37
+
38
+ interface OrderPayment {
39
+ method: string;
40
+ status: string;
41
+ transactionId: string;
42
+ }
43
+
44
+ // Test case: Complex combinations of utility types
45
+ interface PatchOrderWithComplexTypesReq {
46
+ // Nested indexed access - should extract OrderCustomer
47
+ customerCity?: Partial<OrderCustomer["address"]>;
48
+
49
+ // Omit with indexed access - should extract OrderItems
50
+ items?: Omit<OrderItems, "tax"> & Partial<OrderItems["subtotal"]>;
51
+
52
+ // Pick with indexed access - should extract OrderShipping
53
+ shipping?: Pick<OrderShipping, "method"> & Partial<OrderShipping["trackingNumber"]>;
54
+
55
+ // Union of Partial with indexed access
56
+ paymentOrShipping?:
57
+ Partial<OrderPayment["status"]> | Partial<OrderShipping["method"]>;
58
+
59
+ // Array of Partial types
60
+ itemUpdates?: Array<Partial<OrderItem>>;
61
+ }
62
+
63
+ type Params = { orderId: string };
64
+
65
+ interface OrderResponse {
66
+ orderId: string;
67
+ status: string;
68
+ }
69
+
70
+ const PatchOrderWithComplexTypes: Handler<any, PatchOrderWithComplexTypesReq, OrderResponse, Params> = async ({ req }) => {
71
+ return {
72
+ data: {
73
+ orderId: req.params.orderId,
74
+ status: "updated",
75
+ },
76
+ };
77
+ };
78
+
79
+ export default PatchOrderWithComplexTypes;
@@ -0,0 +1,49 @@
1
+ import { Handler, HttpMethod, RouteProps } from "@flink-app/flink";
2
+
3
+ export const Route: RouteProps = {
4
+ path: "/product/:productId",
5
+ method: HttpMethod.patch,
6
+ };
7
+
8
+ // Base interfaces for testing intersection types
9
+ interface ProductDetails {
10
+ name: string;
11
+ description: string;
12
+ price: number;
13
+ }
14
+
15
+ interface ProductInventory {
16
+ stock: number;
17
+ warehouse: string;
18
+ sku: string;
19
+ }
20
+
21
+ interface ProductMetadata {
22
+ createdAt: Date;
23
+ updatedAt: Date;
24
+ createdBy: string;
25
+ }
26
+
27
+ // Test case: Intersection type with Partial indexed access patterns
28
+ interface PatchProductWithIntersectionReq {
29
+ // Intersection of Partial types - should extract ProductDetails and ProductInventory
30
+ update?: Partial<ProductDetails["price"]> & Partial<ProductInventory["stock"]>;
31
+ // More realistic intersection pattern
32
+ fullUpdate?: Partial<ProductDetails> & Partial<ProductInventory> & { notes: string };
33
+ // Intersection with indexed access
34
+ metadataUpdate?: Partial<ProductMetadata["updatedAt"]> & { updatedBy: string };
35
+ }
36
+
37
+ type Params = { productId: string };
38
+
39
+ const PatchProductWithIntersection: Handler<any, PatchProductWithIntersectionReq, ProductDetails, Params> = async ({ req }) => {
40
+ return {
41
+ data: {
42
+ name: "Test Product",
43
+ description: "Test Description",
44
+ price: (req.body.update as any) || 99.99,
45
+ },
46
+ };
47
+ };
48
+
49
+ export default PatchProductWithIntersection;
@@ -0,0 +1,46 @@
1
+ import { Handler, HttpMethod, RouteProps } from "@flink-app/flink";
2
+
3
+ export const Route: RouteProps = {
4
+ path: "/user/:userId",
5
+ method: HttpMethod.patch,
6
+ };
7
+
8
+ // Base interfaces for testing union types
9
+ interface UserProfile {
10
+ firstName: string;
11
+ lastName: string;
12
+ bio: string;
13
+ }
14
+
15
+ interface UserSettings {
16
+ theme: string;
17
+ notifications: boolean;
18
+ language: string;
19
+ }
20
+
21
+ interface UserPreferences {
22
+ emailFrequency: string;
23
+ newsletter: boolean;
24
+ }
25
+
26
+ // Test case: Union type with multiple Partial indexed access patterns
27
+ interface PatchUserWithUnionReq {
28
+ // Union of Partial types - should extract UserProfile, UserSettings, UserPreferences
29
+ data?: Partial<UserProfile["firstName"]> | Partial<UserSettings["theme"]> | Partial<UserPreferences["emailFrequency"]>;
30
+ // More realistic union pattern
31
+ profileUpdate?: Partial<UserProfile> | Partial<UserSettings>;
32
+ }
33
+
34
+ type Params = { userId: string };
35
+
36
+ const PatchUserWithUnion: Handler<any, PatchUserWithUnionReq, UserProfile, Params> = async ({ req }) => {
37
+ return {
38
+ data: {
39
+ firstName: "Test",
40
+ lastName: "User",
41
+ bio: req.body.data as string || "Default bio",
42
+ },
43
+ };
44
+ };
45
+
46
+ export default PatchUserWithUnion;
@@ -1,5 +1,5 @@
1
1
  import { JSONSchema7 } from "json-schema";
2
- import { getJsDocComment } from "../src/utils";
2
+ import { formatValidationErrors, getDataAtPath, getJsDocComment } from "../src/utils";
3
3
 
4
4
  describe("Utils", () => {
5
5
  describe("getJsDocComment", () => {
@@ -15,6 +15,140 @@ describe("Utils", () => {
15
15
  expect(getJsDocComment(comment)).toBe("Hello world\nThis is another line\nThis line contains a * (asterisk)");
16
16
  });
17
17
  });
18
+
19
+ describe("getDataAtPath", () => {
20
+ it("should extract data at root path", () => {
21
+ const data = { foo: "bar" };
22
+ expect(getDataAtPath(data, "/")).toEqual({ foo: "bar" });
23
+ });
24
+
25
+ it("should extract nested data", () => {
26
+ const data = { jobs: [{ id: 1 }, { id: 2 }, { id: 3, result: { seed: "123" } }] };
27
+ expect(getDataAtPath(data, "/jobs/2/result")).toEqual({ seed: "123" });
28
+ });
29
+
30
+ it("should return undefined for non-existent path", () => {
31
+ const data = { jobs: [{ id: 1 }] };
32
+ expect(getDataAtPath(data, "/jobs/5/result")).toBeUndefined();
33
+ });
34
+ });
35
+
36
+ describe("formatValidationErrors", () => {
37
+ it("should format simple validation errors with data context", () => {
38
+ const errors = [
39
+ {
40
+ instancePath: "/name",
41
+ schemaPath: "#/properties/name/type",
42
+ keyword: "type",
43
+ params: { type: "string" },
44
+ message: "must be string",
45
+ },
46
+ ];
47
+ const data = { name: 123 };
48
+
49
+ const formatted = formatValidationErrors(errors, data);
50
+
51
+ expect(formatted).toContain('Path: /name');
52
+ expect(formatted).toContain('Data: 123');
53
+ expect(formatted).toContain('expected string');
54
+ });
55
+
56
+ it("should format missing required property errors", () => {
57
+ const errors = [
58
+ {
59
+ instancePath: "/user",
60
+ schemaPath: "#/properties/user/required",
61
+ keyword: "required",
62
+ params: { missingProperty: "email" },
63
+ message: "must have required property 'email'",
64
+ },
65
+ ];
66
+ const data = { user: { name: "John" } };
67
+
68
+ const formatted = formatValidationErrors(errors, data);
69
+
70
+ expect(formatted).toContain('Path: /user');
71
+ expect(formatted).toContain('Data: {"name":"John"}');
72
+ expect(formatted).toContain('Missing required property: email');
73
+ });
74
+
75
+ it("should format errors for array items", () => {
76
+ const errors = [
77
+ {
78
+ instancePath: "/jobs/5/result",
79
+ schemaPath: "#/properties/jobs/items/properties/result/anyOf",
80
+ keyword: "anyOf",
81
+ params: {},
82
+ message: "must match a schema in anyOf",
83
+ },
84
+ ];
85
+ const data = {
86
+ jobs: [
87
+ { result: { seed: 1 } },
88
+ { result: { seed: 2 } },
89
+ { result: { seed: 3 } },
90
+ { result: { seed: 4 } },
91
+ { result: { seed: 5 } },
92
+ { result: { seed: "wrong-type" } }, // This is item 5 (0-indexed)
93
+ ],
94
+ };
95
+
96
+ const formatted = formatValidationErrors(errors, data);
97
+
98
+ expect(formatted).toContain('Path: /jobs/5/result');
99
+ expect(formatted).toContain('Data: {"seed":"wrong-type"}');
100
+ expect(formatted).toContain('must match a schema in anyOf');
101
+ });
102
+
103
+ it("should truncate very long data", () => {
104
+ const errors = [
105
+ {
106
+ instancePath: "/data",
107
+ schemaPath: "#/properties/data/type",
108
+ keyword: "type",
109
+ params: { type: "string" },
110
+ message: "must be string",
111
+ },
112
+ ];
113
+ const longString = "x".repeat(1000);
114
+ const data = { data: longString };
115
+
116
+ const formatted = formatValidationErrors(errors, data, 100);
117
+
118
+ expect(formatted).toContain('(truncated)');
119
+ expect(formatted.length).toBeLessThan(500);
120
+ });
121
+
122
+ it("should group multiple errors for the same path", () => {
123
+ const errors = [
124
+ {
125
+ instancePath: "/user",
126
+ schemaPath: "#/properties/user/required",
127
+ keyword: "required",
128
+ params: { missingProperty: "email" },
129
+ message: "must have required property 'email'",
130
+ },
131
+ {
132
+ instancePath: "/user",
133
+ schemaPath: "#/properties/user/required",
134
+ keyword: "required",
135
+ params: { missingProperty: "age" },
136
+ message: "must have required property 'age'",
137
+ },
138
+ ];
139
+ const data = { user: { name: "John" } };
140
+
141
+ const formatted = formatValidationErrors(errors, data);
142
+
143
+ // Should only show the path and data once
144
+ const pathCount = (formatted.match(/Path: \/user/g) || []).length;
145
+ expect(pathCount).toBe(1);
146
+
147
+ // But should list both missing properties
148
+ expect(formatted).toContain('Missing required property: email');
149
+ expect(formatted).toContain('Missing required property: age');
150
+ });
151
+ });
18
152
  });
19
153
 
20
154
  const jsonSchemas: JSONSchema7 = {
package/src/FlinkApp.ts CHANGED
@@ -19,7 +19,7 @@ import { FlinkPlugin } from "./FlinkPlugin";
19
19
  import { FlinkRepo } from "./FlinkRepo";
20
20
  import { FlinkResponse } from "./FlinkResponse";
21
21
  import generateMockData from "./mock-data-generator";
22
- import { getPathParams, isError } from "./utils";
22
+ import { formatValidationErrors, getPathParams, isError } from "./utils";
23
23
 
24
24
  const ajv = new Ajv();
25
25
  addFormats(ajv);
@@ -504,9 +504,8 @@ export class FlinkApp<C extends FlinkContext> {
504
504
  const valid = validateReq(req.body);
505
505
 
506
506
  if (!valid) {
507
- log.warn(`${methodAndRoute}: Bad request ${JSON.stringify(validateReq.errors, null, 2)}`);
508
-
509
- log.debug(`Invalid json: ${JSON.stringify(req.body)}`);
507
+ const formattedErrors = formatValidationErrors(validateReq.errors, req.body);
508
+ log.warn(`[${req.reqId}] ${methodAndRoute}: Bad request\n${formattedErrors}`);
510
509
 
511
510
  return res.status(400).json({
512
511
  status: 400,
@@ -563,9 +562,8 @@ export class FlinkApp<C extends FlinkContext> {
563
562
  const valid = validateRes(JSON.parse(JSON.stringify(handlerRes.data)));
564
563
 
565
564
  if (!valid) {
566
- log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response ${JSON.stringify(validateRes.errors, null, 2)}`);
567
- log.debug(`Invalid json: ${JSON.stringify(handlerRes.data)}`);
568
- // log.debug(JSON.stringify(schema, null, 2));
565
+ const formattedErrors = formatValidationErrors(validateRes.errors, handlerRes.data);
566
+ log.warn(`[${req.reqId}] ${methodAndRoute}: Bad response\n${formattedErrors}`);
569
567
 
570
568
  return res.status(500).json({
571
569
  status: 500,
@@ -9,6 +9,7 @@ export enum HttpMethod {
9
9
  post = "post",
10
10
  put = "put",
11
11
  delete = "delete",
12
+ patch = "patch",
12
13
  }
13
14
 
14
15
  type Params = Request["params"];