Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions deno.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@murat/yelix",
"exports": "./mod.ts",
"version": "0.1.27",
"version": "0.1.28",
"license": "MIT",
"nodeModulesDir": "auto",
"tasks": {
Expand All @@ -13,6 +13,7 @@
"deploy": "deploy",
"test": "deno test --allow-net --allow-read --allow-env --ignore=./testing/",
"test:testing": "deno test --allow-net --allow-read --allow-env ./testing",
"test:validation": "deno test --allow-net --allow-read --allow-env ./test/validation",
"test:watch": "deno test --watch --allow-net --allow-read --allow-env --ignore=./testing/",
"test:coverage": "deno test --coverage=coverage --allow-net --allow-read --allow-env --ignore=./testing/",
"test:coverage-html": "deno task test:coverage && deno coverage --html"
Expand All @@ -23,7 +24,6 @@
"@std/assert": "jsr:@std/assert@1",
"hono": "npm:hono@^4.7.0",
"hono-openapi": "npm:hono-openapi@^0.4.4",
"zod": "npm:zod@^3.24.1",
"@/": "./"
},
"publish": {
Expand Down
3 changes: 1 addition & 2 deletions deno.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion jsr.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@murat/yelix",
"version": "0.1.27",
"version": "0.1.28",
"license": "MIT",
"exports": {
".": "./mod.ts",
Expand Down
11 changes: 9 additions & 2 deletions mod.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
export * from "@/src/types/types.d.ts";
export * from "@/src/api/middlewares/requestValidation.ts";
export * from "./src/api/middlewares/requestValidationYelix.ts";
export * from "@/src/api/middlewares/simpleLogger.ts";
export * from "@/src/core/Yelix.ts";
export * from "@/src/utils/cache.ts";
export * from "@/src/OpenAPI/index.ts";
export * from "@/src/test/testClient.ts";

export * from "@/src/validation/inp.ts";
export * from "@/src/validation/ValidationBase.ts";
export * from "@/src/test/testClient.ts";
export * from "@/src/validation/StringZod.ts";
export * from "@/src/validation/FileZod.ts";
export * from "@/src/validation/ObjectZod.ts";
export * from "@/src/validation/NumberZod.ts";
export * from "@/src/validation/ArrayZod.ts";
export * from "@/src/validation/DateZod.ts";
113 changes: 77 additions & 36 deletions src/OpenAPI/Core.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// deno-lint-ignore-file no-explicit-any
import type { OpenAPI, OpenAPIDefaultSchema } from "@/src/OpenAPI/index.ts";
import { z } from "zod";
import type { NewEndpointParams, OpenAPIParams } from "@/src/OpenAPI/index.ts";
import { inp, YelixValidationBase } from "@/mod.ts";

class YelixOpenAPI {
_openAPI: OpenAPI | null = null;
Expand Down Expand Up @@ -39,38 +39,76 @@ class YelixOpenAPI {
return this._openAPI!;
}

private generateExample(schema: z.ZodTypeAny): any {
if (schema instanceof z.ZodString) return "example string";
if (schema instanceof z.ZodNumber) return 42;
if (schema instanceof z.ZodBoolean) return true;
if (schema instanceof z.ZodLiteral) return schema._def.value;
if (schema instanceof z.ZodEnum) return schema._def.values[0];
if (schema instanceof z.ZodArray) {
return [this.generateExample(schema._def.type)];
private generateYelixExample(yelixSchema: YelixValidationBase): any {
const typeRule = yelixSchema.rules.find((r) => r.title === "isValidType");
if (!typeRule) return null;

const type = typeRule.value;
const isDatetime = yelixSchema.rules.some((r) => r.title === "datetime");

if (type === "string") {
if (isDatetime) return new Date().toISOString();
return "example string";
}
if (schema instanceof z.ZodObject) {
if (type === "number") return 42;
if (type === "boolean") return true;
if (type === "file") return "example.txt";

if (type === "object" && "subFields" in yelixSchema) {
const example: Record<string, any> = {};
for (const key in schema.shape) {
example[key] = this.generateExample(schema.shape[key]);
for (
const [key, subSchema] of Object.entries(
yelixSchema.subFields as Record<string, YelixValidationBase>,
)
) {
example[key] = this.generateYelixExample(subSchema);
}
return example;
}
if (schema instanceof z.ZodOptional || schema instanceof z.ZodNullable) {
return this.generateExample(schema._def.innerType);

if (type === "array") {
const arrayTypeRule = yelixSchema.rules.find((r) =>
r.title === "arrayType"
);
if (arrayTypeRule && arrayTypeRule.value) {
return [this.generateYelixExample(arrayTypeRule.value)];
}
return ["example"];
}
return null;
}

private zodToJsonSchema(zodSchema: z.ZodTypeAny): OpenAPIDefaultSchema {
if (!zodSchema) return { type: "object" };
if (zodSchema instanceof z.ZodObject) {
const properties: Record<string, OpenAPIDefaultSchema> = {};
Object.entries(zodSchema.shape).forEach(([key, value]) => {
properties[key] = this.zodToJsonSchema(value as z.ZodTypeAny);
});
return { type: "object", properties };
private yelixZodToJsonSchema(
yelixSchema: YelixValidationBase,
): OpenAPIDefaultSchema {
const schema: OpenAPIDefaultSchema = { type: "string" };

for (const rule of yelixSchema.rules) {
if (rule.title === "isValidType") {
const type = rule.value;
if (type === "string") schema.type = "string";
else if (type === "number") schema.type = "number";
else if (type === "boolean") schema.type = "boolean";
else if (type === "date") schema.type = "string";
else if (type === "file") schema.type = "string";
else if (type === "array") schema.type = "array";
else if (type === "object") {
schema.type = "object";
if ("subFields" in yelixSchema) {
schema.properties = {};
for (
const [key, subSchema] of Object.entries(
yelixSchema.subFields as Record<string, YelixValidationBase>,
)
) {
schema.properties[key] = this.yelixZodToJsonSchema(subSchema);
}
}
}
}
}
return { type: "string" };

return schema;
}

addNewEndpoint(apiDoc: NewEndpointParams) {
Expand All @@ -90,10 +128,12 @@ class YelixOpenAPI {
description: response.description,
content: {
[response.type]: {
schema: this.zodToJsonSchema(response.zodSchema || z.string()),
schema: this.yelixZodToJsonSchema(
response.zodSchema || inp().string(),
),
examples: {
autoGenerated: this.generateExample(
response.zodSchema || z.string(),
autoGenerated: this.generateYelixExample(
response.zodSchema || inp().string(),
),
},
},
Expand All @@ -103,37 +143,38 @@ class YelixOpenAPI {

const parameters = [];
const queries = apiDoc.validation?.query;

if (queries) {
for (const key in queries) {
const query = queries[key];
const queryDescription = apiDoc.query?.[key]?.description;
let queryDefaultDescription = ""; // Markdown

if (query instanceof z.ZodType && query._def?.checks?.length > 0) {
if (query instanceof YelixValidationBase) {
queryDefaultDescription += "###### Validation Rules\n";
}

if (query instanceof z.ZodType) {
const zodRules = query._def.checks || [];
const zodRules = query.rules || [];
for (const rule of zodRules) {
const customDescription = this.getCustomValidationDescription(
rule.kind,
rule.title,
);

if (customDescription) {
queryDefaultDescription += customDescription(rule);
queryDefaultDescription += customDescription(rule.value) + "\n";
} else {
queryDefaultDescription += "- " + rule.kind +
(rule.value ? ": " + rule.value : "") + "\n";
queryDefaultDescription += "- " +
rule.title +
(rule.value ? ": " + rule.value : "") +
"\n";
}
}
}

parameters.push({
name: key,
in: "query",
required: !(query instanceof z.ZodOptional),
schema: this.zodToJsonSchema(query as z.ZodTypeAny),
required: query.hasRule("required"),
schema: this.yelixZodToJsonSchema(query),
description: queryDescription || queryDefaultDescription,
});
}
Expand Down
5 changes: 2 additions & 3 deletions src/OpenAPI/OpenAPI.types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import type { ValidationType } from "@/mod.ts";
import type { ValidationType, YelixValidationBase } from "@/mod.ts";
import type { AllowedLicenses } from "./index.ts";
import type { z } from "zod";

type OpenAPIMethods = "POST" | "GET" | "PUT" | "DELETE" | "PATCH" | "OPTIONS";

Expand Down Expand Up @@ -704,7 +703,7 @@ type OpenAPIParams = {
type AddOpenAPIEndpointResponseParams = {
description?: string;
type: string; // MIME type
zodSchema: z.ZodObject<z.ZodRawShape> | z.ZodString | null;
zodSchema: YelixValidationBase | null;
};

type NewEndpointParams = {
Expand Down
Loading
Loading