التطبيق · فصل ١١

بتتستخدم فين؟ — OpenAPI والـ editors والـ LLMs

OpenAPI 3.1 · OpenAPI 3.0 · nullable · SchemaStore · json.schemas · yaml-language-server · structured outputs · tool calling · strict mode · MongoDB $jsonSchema · RJSF · contract testing · Schema Registry
🎯 الهدف: تعرف كل الأماكن اللي نفس الـ JSON Schema بتشتغل فيها، والفروق اللي لازم تاخد بالك منها في كل مكان.

الفكرة: schema واحدة، استخدامات كتير

API validationOpenAPI docsTS typeseditor autocompleteconfig validationLLM structured outputstool callingMongoDB validatorformscontract testsKafka events
ده أكبر سبب تختار JSON Schema: بتكتب العقد مرة، وأدوات كتير بتفهمه من غير ما تترجمه.

OpenAPI

الـ OpenAPI بتوصف الـ API كله: الـ paths والـ methods والـ responses. وجوّاها، شكل الـ bodies مكتوب JSON Schema:

openapi.yaml
openapi: 3.1.0info: { title: Koshary API, version: 1.0.0 }servers: [{ url: "https://api.koshary.example" }]paths:  /orders:    post:      operationId: createOrder      summary: طلب جديد      security: []      requestBody:        required: true        content:          application/json:            schema: { $ref: "#/components/schemas/CreateOrder" }      responses:        "201": { description: created }        "400": { description: validation error }components:  schemas:    CreateOrder:      type: object      required: [customer, items]      additionalProperties: false      properties:        customer: { type: string, minLength: 2 }        notes: { type: [string, "null"], maxLength: 200 }        items:          type: array          minItems: 1          items: { $ref: "#/components/schemas/Item" }    Item:      type: object      required: [sku, qty]      properties:        sku: { enum: [koshary-small, koshary-large] }        qty: { type: integer, minimum: 1, examples: [2] }
الحاجةOpenAPI 3.0OpenAPI 3.1
الـ JSON Schemaنسخة معدّلة من draft-04/05متوافقة مع 2020-12 بالكامل
يسمح بـ nullnullable: truetype: [string, "null"]
المثالexample (واحد)examples (array)، وexample deprecated
الـ exclusiveMinimumbooleanرقم
الـ const، if/then، prefixItems، $defsمش موجودينموجودين
keywords جنب $refبتتجاهلبتتطبق
تحدد الـ dialectلأjsonSchemaDialect في الـ root
  • الملف ده عدّى من redocly lint: بـ warning واحد بس إن مفيش license.
  • في 3.1 تقدر تاخد order.json من الـ Lab وتحطه زي ما هو تحت components/schemas.
  • الـ discriminator وxml: keywords خاصة بـ OpenAPI، مش من JSON Schema.
  • الأدوات: Swagger UI وRedoc للـ docs، وgenerators للـ clients، وFastify بيطلّع OpenAPI من الـ route schemas بـ @fastify/swagger. راجع الـ docs لدعم 3.1 في كل أداة.
  • لو الأداة لسه 3.0: Zod 4 بيطلّع target: "openapi-3.0" (فصل ١٠).

Autocomplete للـ config files

الـ VS Code (وغيره) بيقرا JSON Schema ويديك autocomplete وhover docs وخط أحمر تحت الغلط وإنت بتكتب.

config.json
{  "$schema": "./config.schema.json",  "port": 3000,  "db": { "url": "postgres://localhost/app" }}
  • الـ $schema جوه ملف JSON: VS Code بيستخدمه على طول. ممكن يبقى path محلي أو URL.
  • خلي بالك: لو الـ schema فيها additionalProperties: false، لازم تسمح بـ "$schema": { "type": "string" } في الـ properties، وإلا الحقل ده نفسه هيبقى غلط.
  • في YAML: بـ extension الـ YAML بتاع Red Hat، حط comment في أول الملف:
docker-compose.yml
# yaml-language-server: $schema=https://json.schemastore.org/docker-compose.jsonservices:  web:    image: nginx
  • من غير ما تكتب حاجة: موقع SchemaStore فيه schemas لمئات الملفات المشهورة (package.json، tsconfig.json، .eslintrc، GitHub workflows، docker-compose…)، وVS Code بيطابقها بالاسم أوتوماتيك. عشان كده بتلاقي autocomplete في tsconfig.json من غير أي إعداد.
  • أو من الـ settings:
.vscode/settings.json
{  "json.schemas": [    {      "fileMatch": ["config/*.json"],      "url": "./schemas/config.schema.json"    }  ]}

Validate الـ config وقت الـ startup

الـ autocomplete بيساعد وإنت بتكتب، بس السيرفر نفسه لازم يرفض config غلط قبل ما يبدأ:

load-config.js
import { readFileSync } from "node:fs";import Ajv from "ajv/dist/2020.js";import addFormats from "ajv-formats";const read = (f) => JSON.parse(readFileSync(new URL(f, import.meta.url), "utf8"));const ajv = new Ajv({ allErrors: true, useDefaults: true });addFormats(ajv);const validate = ajv.compile(read("./config.schema.json"));const config = read("./config.json");if (!validate(config)) {  console.error("config.json فيه مشاكل:");  for (const e of validate.errors) console.error(" ", e.instancePath || "/", e.message, e.params.additionalProperty ?? "");  process.exit(1);}console.log("config تمام", config);

مع config فيه "port": "3000" و"logLevel": "verbose" وpoolmax (typo):

output
config.json فيه مشاكل:  /port must be integer  /logLevel must be equal to one of the allowed values  /db must NOT have additional properties poolmax
  • الـ typo اتلقط: poolmax بدل poolMax. من غير additionalProperties: false كان هيتجاهل بهدوء، والـ pool هيشتغل بالـ default وإنت فاكر إنه 20.
  • ولما الـ config صح: useDefaults حط logLevel: 'info' وpoolMax: 10 من الـ schema.
  • نفس الفكرة لـ env vars: حطهم في object وراجعهم بـ coerceTypes: true.

الـ LLMs: structured outputs والـ tool calling

الـ LLM APIs بتستخدم JSON Schema في مكانين:

Structured outputs

بتبعت schema مع الـ request، والموديل مُجبر يرد بـ JSON مطابق ليها. في OpenAI ده response_format من نوع json_schema ومعاه strict: true. راجع الـ docs للشكل بالظبط.

Tool / function calling

كل tool ليها parameters (أو input_schema في Anthropic، أو inputSchema في MCP) مكتوبة JSON Schema. الموديل بيقرا الـ description بتاع كل حقل عشان يعرف يملاه إزاي.

review.schema.json
{  "type": "object",  "properties": {    "sentiment": { "type": "string", "enum": ["positive", "neutral", "negative"] },    "score": { "type": "integer", "minimum": 1, "maximum": 5, "description": "تقييم من 1 لـ 5" },    "dish": { "type": ["string", "null"], "description": "اسم الأكلة لو اتذكرت، وإلا null" }  },  "required": ["sentiment", "score", "dish"],  "additionalProperties": false}
  • الـ strict mode في OpenAI ليه قيود: كل الحقول لازم تبقى في required، وadditionalProperties: false إجباري في كل object، والـ root لازم object. الحقل “الاختياري” بيتعمل بـ type: [string, "null"] زي dish فوق.
  • وفيه keywords مش مدعومة أو ليها حدود: في الـ strict mode، والقايمة بتتغير. راجع الـ docs قبل ما تعتمد على pattern أو format أو oneOf.
  • اكتب description كويس: دي الـ prompt بتاع كل حقل.
  • Zod أسهل هنا: z.toJSONSchema بيطلّع additionalProperties: false وrequired، والـ SDKs بتقبل Zod مباشرة. راجع الـ docs.
check-llm.js
import Ajv from "ajv/dist/2020.js";import { readFileSync } from "node:fs";const validate = new Ajv({ allErrors: true }).compile(JSON.parse(readFileSync("./review.schema.json", "utf8")));// ردود جاية من الموديل (نص)const replies = [  '{"sentiment":"positive","score":5,"dish":"كشري"}',  '{"sentiment":"Positive","score":"5"}',  'أكيد! ده الـ JSON: {"sentiment":"positive"}',];for (const text of replies) {  let data;  try {    data = JSON.parse(text);  } catch {    console.log("مش JSON أصلًا");    continue;  }  console.log(validate(data) ? "تمام" : validate.errors.map((e) => `${e.instancePath || "/"} ${e.message}`).join(" | "));}
output
تمام/ must have required property 'dish' | /sentiment must be equal to one of the allowed values | /score must be integerمش JSON أصلًا
  • لو فشل: ابعت الـ errors للموديل تاني في رسالة (“صلّح دول”) وجرّب مرة أو اتنين، وبعدين fallback.

MongoDB: الـ validator جوه الداتابيز

mongosh
db.createCollection("orders", {  validator: {    $jsonSchema: {      bsonType: "object",      required: ["customer", "total", "status"],      properties: {        customer: { bsonType: "string", minLength: 2 },        total: { bsonType: "int", minimum: 0 },        status: { enum: ["new", "paid", "shipped"] },      },    },  },  validationLevel: "strict",  // أو "moderate": سيب الـ documents القديمة الغلط  validationAction: "error",  // أو "warn": اكتب في الـ log بس});
  • الـ $jsonSchema: مبني على draft-04، ومعاه bsonType عشان أنواع زي int وlong وdecimal وdate وobjectId.
  • مش كل الـ keywords موجودة: حسب الـ docs مفيش $ref ولا $schema ولا default ولا definitions ولا format ولا id. راجع الـ docs للنسخة بتاعتك.
  • الفايدة: آخر خط دفاع. حتى لو script قديم أو حد من الـ shell كتب في الـ collection، الـ document الغلط بيترفض.
  • الـ moderate: بيطبق الـ validation على الـ inserts وعلى تعديل الـ documents اللي هي أصلًا valid بس. مفيد وإنت بتضيف validation على collection قديمة.
  • ملحوظة أمانة: المثال ده مكتوب من الـ docs ومتجرّبش هنا، لأن MongoDB 8 مرضيش يشتغل على الـ kernel بتاع جهاز التجربة.

حاجات تانية بتستخدمها

  • الـ forms: react-jsonschema-form (RJSF) وJSON Forms بيبنوا form كاملة من schema، ومعاها uiSchema للشكل.
  • الـ contract testing: في الـ tests، اعمل validate لكل response على الـ schema بتاعة الـ OpenAPI. لو حد غيّر الـ API وكسر العقد، الـ test يقع. وPostman فيه pm.response.to.have.jsonSchema(schema).
  • الـ Kafka Schema Registry: (Confluent وغيره) بيدعم Avro وProtobuf وJSON Schema، وبيرفض schema جديدة لو بتكسر الـ compatibility (BACKWARD / FORWARD / FULL).
  • الـ Kubernetes CRDs: بتوصف الـ custom resources بـ openAPIV3Schema، وهي subset من JSON Schema.
  • الـ Helm: ملف values.schema.json بيتراجع عليه الـ values.yaml وقت helm install.
  • الـ VS Code extensions: الـ settings اللي بتعرّفها في contributes.configuration بتتكتب JSON Schema.
  • الـ docs generators: أدوات بتحوّل الـ schema لصفحة docs فيها كل حقل ووصفه.

خلّصت الفصل لما

🧠 اختبر نفسك
١) schema فيها nullable: true، شغالة في OpenAPI 3.0. نقلتها لـ 3.1، إيه اللي يتغير؟
في 3.1 مفيش nullable، اكتب type array فيها "null". وكمان example يبقى examples.
٢) ليه config.json فيه $schema ممكن يفشل في الـ validation بتاعتك؟
لأن الـ schema فيها additionalProperties: false ومش معرّفة $schema كـ property. ضيفها كـ string.
٣) في OpenAI strict mode، عايز حقل اختياري. تعمل إيه؟
حطه في required برضه، وخلي الـ type يقبل null، والموديل يرجّع null لو مفيش قيمة.
٤) ليه تحط $jsonSchema في MongoDB لو الـ API بيعمل validation أصلًا؟
آخر خط دفاع ضد أي حد بيكتب في الداتابيز من غير الـ API: scripts، migrations، shell، أو service تانية.