چرا JSON Schema تولیدشده خودکار مدام داده واقعی را رد میکند
جمعبندی: schemaهای تولیدشده غالباً داده معتبر را مسدود میکنند. یاد بگیرید چرا نمونههای پرمحتواتر schemaهای بدتری میسازند، سپس گردش کار: نمونهگیری چندرکوردی، تکمیل معنایی، مکانیابی خطا.

جمعبندی: اول مرز required/optional را دستی همگرا کنید — ۵ تا ۲۰ رکورد چندشکلی با «همه الزامی» خاموش بدهید — سپس schema را تولید و format، enum و additionalProperties را دستی اضافه کنید؛ یک نمونه کاملفیلد همیشه بیش از حد سختگیر خواهد بود.
تولیدکننده فقط همان یک رکوردی را میبیند که جایگذاری کردهاید. «این رکورد ایمیل دارد» را «هر رکورد باید ایمیل داشته باشد» میخواند، پس هر چه نمونههایتان کاملتر شوند، بیشتر طلب میکند.
۱. تولیدکننده نگاشت ساختاری انجام میدهد، نه فهم کسبوکار
تکهای JSON به تولیدکننده بدهید و دقیقاً یک کار میکند: ساختار مقدارها را طی میکند و هر گره را به کلیدواژه schema ترجمه میکند. کل جدول نگاشت ثابت است:
| مقدار در نمونه | نوع استنتاجشده | یادداشت |
|---|---|---|
"hello" |
{"type": "string"} |
ایمیل، تاریخ، UUID را تشخیص نمیدهد |
42 |
{"type": "number"} |
عدد صحیح بهعنوان integer استنتاج نمیشود |
3.14 |
{"type": "number"} |
همان بالا |
true |
{"type": "boolean"} |
— |
null |
{"type": "null"} |
فقط null را میشناسد، هرگز «nullable» را |
[1, "a"] |
{"type": "array", "items": {"type": ["number", "string"]}} |
نوعهای عنصر با union جمع میشوند |
{"a": 1} |
{"type": "object", "properties": {...}, "required": [...]} |
required اینجا نکته اصلی است |
همه دردسر در ردیف آخر است. required از نوعها استنتاج نمیشود؛ از «این کلید در این نمونه وجود دارد» استنتاج میشود. وقتی نمونه یک رکورد است، آن گزاره معادل «هر کلید الزامی است».
و دادهای که APIهای واقعی برمیگردانند تقریباً همیشه نیمی از فیلدهایش اختیاری است. تعارض از اینجا شروع میشود.
۲. پنج رکورد همشکل، پنج نرخ پذیرش
با اندازهگیری شروع کنید. هر کسی پنج رکورد زیر را ببیند میگوید «اینها همشکلاند»:
[
{
"id": 1,
"name": "Ada",
"email": "ada@example.com",
"tags": ["a"],
"score": 10
},
{ "id": 2, "name": "Bob", "tags": [], "score": 7 },
{
"id": 3,
"name": "Cy",
"email": "cy@example.com",
"tags": ["a", "b"],
"score": 8.5
},
{ "id": 4, "name": "Dee", "email": null, "tags": ["c"], "score": 9 },
{
"id": 5,
"name": "Eve",
"email": "eve@example.com",
"tags": ["d"],
"score": 6,
"admin": true
}
]
email در ۳ رکورد از ۵ ظاهر میشود، admin فقط یک بار، و رکورد ۲ آرایه tags خالی دارد.
حالا از هر رکورد جداگانه schema تولید کنید و سپس هر نتیجه را برای اعتبارسنجی هر پنج رکورد استفاده کنید (اعتبارسنج: ajv 8):
| تولیدشده از | تعداد کلید | required تولیدشده |
پذیرش از ۵ |
|---|---|---|---|
| رکورد ۱ | ۵ | id, name, email, tags, score |
۳ رکورد |
| رکورد ۲ | ۴ | id, name, tags, score |
۵ رکورد |
| رکورد ۳ | ۵ | id, name, email, tags, score |
۳ رکورد |
| رکورد ۴ | ۵ | id, name, email, tags, score |
۱ رکورد |
| رکورد ۵ | ۶ | id, name, email, tags, score, admin |
۱ رکورد |
هیچیک از پنج schema درست نیست، اما تنها schemaیی که همه داده را میپذیرد از رکورد ۲ میآید — رکوردی که email ندارد. به دلیلی ساده برنده میشود: کلیدهایی که هرگز ظاهر نمیشوند هرگز در required نوشته نمیشوند.
رکورد ۴ حتی آموزندهتر است: مقدار emailش null است، پس تولیدکننده {"type": "null"} سختکد میکند، و چهار رکورد دیگر با ایمیل رشتهای همه رد میشوند.
۳. چرا «کاملترین رکورد را جایگذاری کن» بدترین انتخاب است
شهود میگوید «نمونهای با بیشترین فیلد جایگذاری کن تا schema کامل شود». اندازهگیریها دقیقاً برعکس را نشان میدهند — و یک طرف ثالث آزمون کنترلشده بزرگتری اجرا کرد.
absurdtools از ۳۰۰۰ «خانواده داده» تصادفی، ۸ رکورد در هر خانواده، با حضور هر فیلد اختیاری تقریباً نیمی از اوقات، استفاده کرد و اندازه گرفت schemaیی که از نمونههای با تعداد کلید مختلف تولید میشود چه مقدار از داده را میپذیرد:
| تعداد کلید نمونه | سهم داده خانواده پذیرفتهشده |
|---|---|
| 2 | 100.0% |
| 3 | 56.8% |
| 4 | 34.8% |
| 5 | 23.4% |
| 6 | 17.7% |
| 7 | 14.9% |
نتیجه «افت جزئی» نیست، وارونگی کامل است: نمونه حداقلی در ۹۵٪ خانوادهها سستترین schema را تولید کرد، در حالی که کاملترین نمونه فقط ۰.۱٪ اوقات موفق شد.
دلیل در معناشناسی required است که یعنی «همه»؛ اختیاری بودن فقط از طریق غیبت قابل مشاهده است. جایگذاری رکوردی با هر فیلد به تولیدکننده میگوید «هیچیک از این کلیدها نباید حذف شود»؛ جایگذاری رکوردی با فیلد گمشده مدرکی میدهد که «این کلید میتواند غایب باشد».
یک جمله به یاد بسپارید: ارزش یک نمونه کامل بودنش نیست، بلکه کدام غیبتها را پوشش میدهد.
چند رکورد کافی است
همان دسته آزمایشها سرعت همگرایی را هم اندازه گرفت: با انباشتن رکورد یکبهیک تحت قاعده «کلید تا وقتی الزامی میماند که هر رکورد تاکنون آن را داشته باشد»، میانه تعداد رکورد برای همگرایی required به مجموعه الزامی واقعی ۳ است؛ ۶ رکورد حدود نود درصد خانوادهها را پوشش میدهد و ۱۲ همه را.
پس نمونهگیری ۵ تا ۱۰ رکورد روزمره بهترین نسبت ارزش به تلاش است؛ تحمیل بیست یا بیشتر بهره اضافه بسیار کمی میدهد.
اما یک پیششرط هست: این رکوردها باید از موقعیتهای واقعاً متفاوت بیایند. پنج رکورد کپیشده از یک قالب از رکورد دوم به بعد هیچ اطلاعات جدیدی نمیدهند — مجموعه فیلدهایشان یکسان است و required نمیتواند با یک کلید باریک شود.
۴. اصلاح: چند رکورد را یکجا بدهید و بگذارید غیبتها حرف بزنند
چون نمونهگیری تکرکوردی ناگزیر بیش از حد سختگیر است، رویکرد درست این است که تولیدکننده چند رکورد را یکجا ببیند و required را با قاعده «الزامی فقط اگر هر رکورد آن را دارد» همگرا کند.
تولیدکننده این سایت آرایه را مستقیم میپذیرد و کلید «همه الزامی» دارد. خاموشش کنید و بر اساس تعداد رخداد تصمیم میگیرد: فقط کلیدهایی که هر رکورد حمل میکند در required نوشته میشوند.
همان پنج رکورد — این بار در یک آرایه ترکیب و با هم استنتاج کنید، سپس items را بهعنوان قرارداد عنصر بگیرید:
| رکوردهای دادهشده | required استنتاجشده |
اعتبارسنجی هر ۵ |
|---|---|---|
| ۱ رکورد | id, name, email, tags, score |
۳ پاس |
| ۲ رکورد | id, name, tags, score |
۴ پاس |
| ۳ رکورد | id, name, tags, score |
۴ پاس |
| ۴ رکورد | id, name, tags, score |
۵ پاس |
| ۵ رکورد | id, name, tags, score |
۵ پاس |
required بهمحض افزوده شدن رکورد چهارم همگرا میشود و بعدش تغییر نمیکند. email و admin درست در properties مینشینند اما نه در required. و چون email رکورد ۴ برابر null است، استنتاج چندرکوردی آن را به {"type": ["string", "null"]} ترکیب میکند، پس هم رشته و هم null پاس میشوند — چیزی که نمونهگیری تکرکوردی هرگز نمیتواند.
این با یافته absurdtools هممرتبه است که «required پس از میانه ۳ رکورد همگرا میشود».
استنتاج چندرکوردی نقاط کور خودش را دارد: اگر email در این رکوردها تصادفاً در همه null باشد (مثلاً همه از حسابهایی که پروفایل ندارند)، نتیجه استنتاج {"type": "null"} میشود و ایمیلهای رشتهای همچنان رد میشوند. به همین ترتیب، اگر هر مقدار tags آرایه خالی باشد، نوع عنصر اصلاً استنتاج نمیشود.
پس نمونهگیری یک الزام سخت دیگر دارد: هر شکل از هر فیلد باید دستکم یک بار ظاهر شود. یک رشته، یک عدد، یک null و یک آرایه خالی بیاورید؛ نگذارید هیچ شکلی کاملاً غایب باشد.
بهطور مشخص:
- ۵ تا ۲۰ پاسخ از لاگ، mock یا درخواست واقعی بکشید و حتماً ردیفهای غیرعادی را بگنجانید (آن با فیلد خالی، آن با null، آن با آرایه خالی)؛
- آنها را به آرایه JSON مونتاژ و در تولیدکننده جایگذاری کنید؛
- «همه الزامی» را خاموش و draft 2020-12 را انتخاب کنید؛
- نتیجه
type: arrayبهعلاوهitemsاست؛ بخشitemsرا بهعنوان قرارداد یک رکورد بگیرید؛ - بلافاصله نمونههای اصلی را با این schema اعتبارسنجی کنید — ۱۰۰٪ باید پاس شوند. اگر حتی یکی شکست بخورد، نمونهگیریتان کافی نیست؛ برگردید و رکورد اضافه کنید.
گام ۵ ارزانترین خودبررسی است. اگر تولیدکننده حتی نمیتواند دادهای که به آن دادهاید را جا دهد، روش نمونهگیریتان اشتباه است و رفتن جلوتر بیفایده است.
۵. چهار چیزی که تولیدکننده نمیتواند پر کند — باید دستی بنویسید
حتی پس از دادن چند رکورد، موارد زیر همچنان قابل استنتاج نیستند و باید دستی اضافه شوند:
| قطعه گمشده | رفتار اندازهگیریشده | پیامد | چگونه اصلاح کنیم |
|---|---|---|---|
| قالب رشته | "a@b.com" فقط {"type": "string"} تولید میکند |
اعتبارسنجی "not an email" پاس میشود |
"format": "email" اضافه و اعلانهای format را فعال کنید |
| بازهها و enumها | نمیتواند ببیند score به ۰ تا ۱۰۰ محدود است یا status فقط سه مقدار دارد |
عددهای منفی، مقادیر عظیم و مقادیر enum آشغال همه پاس میشوند | minimum / maximum / enum اضافه کنید |
| سیاست فیلد ناشناخته | additionalProperties تولید نمیشود |
کلید غلطنویس بیصدا پذیرفته میشود، که از خطا خطرناکتر است | "additionalProperties": false برای فایلهای سبک پیکربندی اضافه کنید |
| معناشناسی nullable و مجموعه خالی | یک نمونه حاوی null به {"type": "null"} قفل میشود؛ آرایه خالی "items": {} تولید میکند |
مقادیر معتبر رد میشوند، یا عناصر آرایه کاملاً بیقید میمانند | به ["string", "null"] تغییر دهید، و به آرایههای خالی عناصر واقعی بدهید |
ردیف سوم شایسته اشاره جدا است: مقدار پیشفرض additionalProperties «مجاز» است. یعنی فایلی که timeout را timeuot نوشته اعتبارسنجی را پاس میکند و بعد در زمان اجرا بیصدا هیچ کاری نمیکند. برای schemaهای سبک پیکربندی، این موردی است که باید صریحاً خاموش کنید.
format تله هم دارد: از draft 2019-09، format به واژگان حاشیهنویسی تعلق دارد و بهصورت پیشفرض فقط حاشیهنویسی میکند نه اعمال. اگر format: "email" اضافه کردید و مقادیر غیرقانونی همچنان پاس میشوند، در هشت از ده مورد اعتبارسنج validateFormats یا افزونه قالب مربوط را فعال نکرده — نه اینکه schema اشتباه باشد.
نکتهای درباره انتخاب draft: اگر schema به OpenAPI 3.1 میرود، باید draft 2020-12 باشد، چون 3.1 مستقیماً همان نسخه را جاسازی میکند؛ زنجیرههای ابزار قدیمیتر (اکوسیستم Swagger 2.0 و برخی پیادهسازیهای $ref) همچنان حول draft-07 میگردند. این دو برای کلیدواژههای همسایه $ref و کلیدواژههای مربوط به آرایه رفتار متفاوت دارند، پس در یک مجموعه schema مخلوطشان نکنید.
۶. برای APIهای شخص ثالث بدون مستندات، قرارداد را از نمونه پاسخها مهندسی معکوس کنید
یکپارچهسازی callback شخص ثالث، همکاری با شریکی که مستندات نمینویسد، یا نگهداری سیستم قدیمی دهساله — همه یعنی «داده در دست است، مشخصات نه». اینجا تولیدکننده صرفاً راحتی نیست؛ سریعترین راه به قرارداد است.
روش همان بالا است، با دو الزام اضافه:
یک: در طول زمان و در حالتهای مختلف نمونه بگیرید. فیلدهای شخص ثالث غالباً با وضعیت کسبوکار تغییر میکنند — یک تخفیف یک discount اضافه میکند، سفارش بازپرداخت refundAt اضافه میکند و پاسخ خطا اصلاً data ندارد. گرفتن یک snapshot یعنی دیدن فقط یک حالت.
چند موفقیت، چند شکست و چند نتیجه خالی را یکجا بدهید: error و data غالباً انحصاری ظاهر میشوند و استنتاج چندرکوردی هر دو را در properties میگذارد و از required بیرون میگذارد — دقیقاً مطابق واقعیت.
دو: schema را در مخزن نگه دارید و یک بررسی روزانه بچسبانید. پاسخهای واقعی امروز را با آن اعتبارسنجی کنید و پیش از کاربرانتان از فیلدهایی که بیصدا ناپدید یا نوعشان عوض میشود باخبر شوید.
// Run once a day: contract-drift alert
const drifted = records.filter((record) => !validate(record));
if (drifted.length > 0) {
console.warn(
`${drifted.length} responses do not match the contract; first difference:`,
validate.errors,
);
}
ارزش این رویه «صرفهجویی در نوشتن دستی» نیست، تبدیل توافق شفاهی به هشدار اجراشدنی است. شخص ثالث هرگز از قبل نمیگوید فیلدی را عوض کرد، اما تغییر قطعاً در لاگهای شما ظاهر میشود.
یک مرز را روشن بکشید: schema مهندسیمعکوسشده توصیف میکند طرف مقابل الان چه برمیگرداند، نه چه قول میدهد برگرداند. اولی برای پایش است؛ فقط دومی به قرارداد یا مستندات API تعلق دارد.
۷. پس از تکمیل، خطاهای اعتبارسنجی را به فیلدهای مشخص برگردانید
وقتی schema قابل استفاده شد، دغدغه بعدی خطاهای خواناست. یک شیء خطای ajv پنج فیلد دارد و چهار مورد اول مفیدند:
| فیلد | معنا | مثال (id: 0 نقض minimum: 1) |
|---|---|---|
instancePath |
JSON Pointer به مقدار خطادار در داده | "/id" |
schemaPath |
مکان قاعده تحریکشده در schema | "#/properties/id/minimum" |
keyword |
نام قید شکستخورده | "minimum" |
params |
جزئیات ماشینخوان آن قید | { "comparison": ">=", "limit": 1 } |
message |
نثر انگلیسی که بین نسخهها تغییر میکند، تجزیهاش نکنید | "must be >= 1" |
یک عدمتقارن آسان برای زمین خوردن: وقتی required شکست میخورد، instancePath به شیء والد اشاره میکند (معمولاً رشته خالی، یعنی ریشه)، نه به فیلد گمشده. نام کلید گمشده در params.missingProperty است. به همین ترتیب، وقتی additionalProperties شکست میخورد، نام کلید اضافه در params.additionalProperty است.
پس هنگام نگاشت خطاها به «فیلد → پیام»، اول این دو حالت خاص را مدیریت کنید:
import Ajv from "ajv";
const ajv = new Ajv({ allErrors: true });
const validate = ajv.compile(schema);
/** Convert the ajv error array into { fieldPath: [message] } for form highlighting */
function groupByField(errors) {
const grouped = {};
for (const error of errors) {
// For required and additionalProperties, the offending field lives in params, not in instancePath
const field =
error.keyword === "required"
? error.params.missingProperty
: error.keyword === "additionalProperties"
? error.params.additionalProperty
: error.instancePath.replace(/^\//u, "").split("/").join(".") ||
"(root)";
(grouped[field] ??= []).push(error.message);
}
return grouped;
}
if (!validate(data)) console.log(groupByField(validate.errors));
allErrors: true اختیاری نیست. در پیکربندی پیشفرض ajv روی نخستین خطا میایستد، پس کاربر یک فیلد را درست میکند، دوباره میفرستد، خطای بعدی را میگیرد و برای پر کردن یک فرم سه رفتوبرگشت لازم دارد. اگر از ajv-errors برای پیامهای سفارشی هم استفاده میکنید، بدون این کلید افزونه بیصدا فقط برخی پیامها را جایگزین میکند.
۸. یک فرایند انتشار قابل بازاستفاده
گامهای بالا را به یک روال ثابت تبدیل کنید و هر بار API جدیدی یکپارچه میکنید دنبالش بروید:
- نمونهگیری: ۵ تا ۲۰ پاسخ واقعی بگیرید و عمداً آنهایی با فیلد خالی، null، آرایه خالی و فیلد اضافه را انتخاب کنید؛
- استنتاج: آنها را یکجا بهصورت آرایه در تولیدکننده جایگذاری کنید، «همه الزامی» را خاموش کنید و
itemsرا بگیرید؛ - تکمیل معناشناسی:
format،enum،minimum/maximum،minItemsوadditionalPropertiesرا یکییکی اضافه کنید؛ - خودبررسی: نمونههای اصلی باید ۱۰۰٪ پاس شوند، وگرنه به گام ۱ برگردید و نمونه اضافه کنید؛
- اثبات منفی: برای هر قید یک ورودی «باید شکست بخورد» آماده و تأیید کنید واقعاً شکست میخورد — آزمون فقط مسیر پاس همانند آزمون نکردن است؛
- کامیت: schema را در کنترل نسخه بگذارید، نمونههای شکستخورده را بهعنوان مورد آزمون نگه دارید و هر بار API فیلدی را عوض کرد اول آزمونها را اجرا کنید.
آزمون منفی گام ۵ آسانترین برای رد کردن است. افزودن کلید به required بدون هرگز تأیید اینکه کلید گمشده خطا میدهد یعنی شرطبندی روی اینکه اعتبارسنج با مدل ذهنی شما موافق است.
import { describe, expect, it } from "vitest";
describe("user schema", () => {
it("accepts a complete record", () => {
expect(validate({ id: 1, name: "Ada", tags: [], score: 10 })).toBe(true);
});
it("rejects a record without name", () => {
expect(validate({ id: 2, tags: [], score: 7 })).toBe(false);
// Also assert the reason, so the test doesn't pass because some other field failed
expect(
validate.errors?.some((e) => e.params?.missingProperty === "name"),
).toBe(true);
});
});
آن اعلان آخر مهم است. اعلان فقط false میتواند حتی پس از شکستن schema سبز بماند — شکست تا آن موقع ممکن است از دلیلی کاملاً متفاوت بیاید.
۹. پرسشهای متداول
آیا schemaیی که تولیدکننده میسازد میتواند مستقیم به تولید برود
نه. فقط snapshot ساختار است. دستکم باید formatها، بازهها، enumها و سیاست فیلد ناشناخته را اضافه کنید، و سپس هم نمونههای اصلی و هم نمونههای منفی را از آن عبور دهید. تولیدکننده «تایپ کردن دستی properties خستهکننده است» را حل میکند، نه «قواعد کسبوکار را تصمیم میگیرد».
برای سیستم کلیدواژه نویسندگی schema، تقسیم $ref و کلیدواژههای ترکیب، JSON Schema: از مبانی تا تولید را ببینید.
چرا رکوردی با بیشترین فیلد داده بیشتری را رد میکند
چون required از «آنچه در نمونه ظاهر شد» جمع میشود. نمونه کاملفیلد هر کلید اختیاری را الزامی میکند، در حالی که بیشتر آن کلیدها در داده واقعی غایباند. دادن چند رکورد، یا رکوردی که عمداً فیلدها را حذف میکند، شما را به حقیقت نزدیکتر از کاملترین نمونه میکند.
آیا عدد صحیح بهعنوان integer استنتاج میشود
تولیدکننده این سایت این کار را نمیکند: هم عدد صحیح و هم شناور بهصورت number خروجی میشوند، پس schemaیی که از {"price": 5} تولید شده 5.99 را میپذیرد. این از حوادثی مانند «نمونه تصادفاً عدد صحیح بود، پس تولید قیمتهای اعشاری را رد میکند» جلوگیری میکند. برخی ابزارهای شخص ثالث از مقدار integer استنتاج میکنند، پس با فیلدهای پول مخصوصاً مراقب باشید.
برای جزئیات دقت عدد و مرزهای نوع، شش نوع داده JSON و تلههای دقت عدد را ببینید.
format اضافه کردم اما اثری ندارد
از draft 2019-09، format بهصورت پیشفرض فقط حاشیهنویسی است. یا اعلانهای format را در اعتبارسنج صریحاً فعال کنید (ajv به ajv-formats نیاز دارد)، یا برای قید سخت از pattern استفاده کنید. فرض نکنید افزودنش چیزی را مسدود میکند.
required خطا میدهد، اما فیلد واضحاً در داده هست
اول instancePath را ببینید. خطای required به شیء والد اشاره میکند، یعنی کلید گمشده در آن سطح نیست — علتهای رایج سطح تودرتویی اشتباه (/profile/name با name در ریشه یکی نیست)، تفاوت حروف بزرگ و کوچک (userId در برابر userID) یا حذف کامل undefined توسط سریالایزر بالادست است.
پیش از بررسی، تأیید کنید خود سند JSON معتبر است: کامنت، ویرگول انتهایی و موارد مشابه در مرحله تجزیه شکست میخورند، پیش از آنکه schema فرصت پیدا کند. تجزیه و قالببندی JSON مستقیم به خط خطادار اشاره میکند.
چند رکورد نمونه بگیرم و کدام draft استفاده کنم؟
نمونهگیری ۵ تا ۱۰ رکورد روزمره بهترین ارزش را میدهد؛ required با میانه ۳ رکورد همگرا میشود و ۱۲ رکورد همه خانوادهها را پوشش میدهد. هر رکورد باید از موقعیتی واقعاً متفاوت بیاید و هر شکل از هر فیلد دستکم یک بار ظاهر شود. draft را به 2020-12 سنجاق کنید، چون OpenAPI 3.1 فقط همان نسخه را میپذیرد.
چرا پیشفرض سهلگیر additionalProperties خطرناکتر است؟
پیشفرض هیچ کلیدواژهای تولید نمیکند، پس کلید غلطنویس بیصدا پذیرفته میشود، اعتبارسنجی را پاس میکند و بعد در زمان اجرا شکست میخورد — تشخیصش از خطای مستقیم سختتر است. schemaهای سبک پیکربندی باید "additionalProperties": false را صریح تنظیم کنند، در حالی که schemaهای API باید اول هزینه سازگاری را بسنجند.
آیا schema مهندسیمعکوسشده را بهعنوان دروازه سخت استفاده کنم؟
نه. schema مهندسیمعکوسشده توصیف میکند طرف مقابل الان چه برمیگرداند، نه چه قول میدهد برگرداند، پس به پایش و هشدار انحراف تعلق دارد. هر چیزی که به قرارداد یا مستندات API میرود باید قرارداد قولدادهشده باشد.