JSON Toolkit

জেসন স্কিমা কন্ট্রাক্ট টেস্টিং: ভ্যালিডেশন নিয়মগুলো CI-তে ঢেলে দিন

সংক্ষেপে: স্কিমা লেখা শেষ হওয়াই শেষ নয় — সেখান থেকেই অবনতি শুরু হয়। একটা টাইপো নীরবে ভ্যালিডেশন শিথিল করে, একটা রিফ্যাক্টরিং সব ফিক্সচার ভেঙে দেয়। এই নির্দেশিকায় একটি বাস্তবসম্মত কন্ট্রাক্ট টেস্টিং কাঠামো ব্যাখ্যা করা হয়েছে: পজিটিভ, নেগেটিভ ও স্ন্যাপশট ফিক্সচার, স্কিমা নিজের লিন্ট, একবার কম্পাইল করে পুনর্ব্যবহার এবং চারটি CI গেট যা কন্ট্রাক্ট ভাঙাকে লাল বিল্ডে পরিণত করে।

মধ্যম~8 মিনিট পড়াপ্রকাশ 2026-11-05আপডেট 2026-11-05
জেসন স্কিমা কন্ট্রাক্ট টেস্টিং ও CI ভ্যালিডেশন গেটের চিত্র

উপসংহার: স্কিমা একটি কোড, আর কোডের টেস্ট দরকার। পজিটিভ ফিক্সচার প্রমাণ করে সে যাকে পাস করানো উচিত তা পাস করায়; নেগেটিভ ফিক্সচার প্রমাণ করে যাকে আটকানো উচিত তা আটকায়; স্ন্যাপশট অনিচ্ছাকৃত পরিবর্তন পাহারা দেয়। শেষে ajv compile আর --ci স্ন্যাপশট আপনার পাইপলাইনে রাতের প্রহরী।

ভ্যালিডেশন নিয়ম ভাঙার মুহূর্তে কিছুই এরর ছোঁড়ে না: গ্রহণ চওড়া হলে কেউ টের পায় না, বাতিল কঠোর হলে পুরো দল তা বইতে হয়। স্কিমা কোনো কনফিগ ফাইল নয় — এটি এমন কোড যা কেউ টেস্ট করে না।

১. স্কিমা ভাঙল — কে প্রথম জানবে

একটি সত্যিকারের ফাঁদ দিয়ে শুরু করি। ২০২০-১২ ড্রাফট অনুযায়ী স্কিমা ঘোষণা করে Ajv-র ডিফল্ট প্যাকেজ দিয়ে কম্পাইল করলে সঙ্গে সঙ্গে এক্সসেপশন ছোটে:

Error: no schema with key or ref "https://json-schema.org/draft/2020-12/schema"

Ajv 8-এর ডিফল্ট এন্ট্রি শুধু draft-07 চেনে; ২০২০-১২-এর জন্য ajv/dist/2020 থেকে ইমপোর্ট করতে হয়। আরেক ফাঁদ: strict মোডে format: "email" লিখলেন কিন্তু ajv-formats ইনস্টল করলেন না — কম্পাইলেও ব্যর্থ হবে:

Error: unknown format "email" ignored in schema at path "#/properties/email"

এই এররগুলো প্রথম কম্পাইলেই লোকালি দেখা গেলে ভালো। সমস্যা: অনেক প্রজেক্ট কম্পাইল রিকোয়েস্ট আসা পর্যন্ত পিছিয়ে রাখে, আর ভাঙা স্কিমা প্রথম শিকারের অপেক্ষায় থাকে। আরও লুকানো বিপদ অর্ধভাঙা স্কিমা: required ভুলে অ্যারের বদলে স্ট্রিং হলে কম্পাইলে schema is invalid: data/required must be array ছোটে — কিন্তু এই ধাপ CI-তে কখনো চলেনি হলে সে প্রোডাকশনে ঘুমিয়ে থাকে।

তাই প্রথম গেট ডেটা ভ্যালিডেট করে না — স্কিমাকেই ভ্যালিডেট করে। একটি কমান্ড:

node scripts/lint-schemas.mjs
// scripts/lint-schemas.mjs
import { readFileSync, readdirSync } from 'node:fs';
import Ajv2020 from 'ajv/dist/2020.js';
import addFormats from 'ajv-formats';

const ajv = new Ajv2020({ allErrors: true, strict: true });
addFormats(ajv);
for (const file of readdirSync('schemas')) {
  const schema = JSON.parse(readFileSync(`schemas/${file}`, 'utf8'));
  ajv.compile(schema);
  console.log(`ok ${file}`);
}

কেন npx ajv compile নয়: ajv v8 প্যাকেজে CLI নেই, তাই npx ajv could not determine executable to run দিয়ে ব্যর্থ হয়; CLI আলাদা ajv-cli প্যাকেজে (আমাদের পরিমাপে 5.0.0)। কিন্তু ajv-cli 5 কেবল draft-07 ও draft-2019-09 সমর্থন করে — প্রথম অধ্যায়ের 2020-12 স্কিমার দিকে তাক করলে একই no schema with key or ref "https://json-schema.org/draft/2020-12/schema" ছোঁড়ে। তাই 2020-12 প্রোজেক্টে লিন্ট হবে নিবন্ধের একই Ajv2020 এন্ট্রি দিয়ে; npx ajv-cli compile -s '**/*.schema.json' --strict কেবল draft-07 প্রোজেক্টের জন্য।

লিন্টারের মতো চালান: টাইপো, অপ্রচলিত কীওয়ার্ড, নিবন্ধিত নয় এমন ফরম্যাট PR ধাপেই ধরা পড়বে। এটাই স্কিমা টেস্টিং আর সাধারণ কোড টেস্টিংয়ের প্রথম মিল: প্রথমে নিশ্চিত করুন কম্পাইল হয়।

২. ত্রয়ী: পজিটিভ, নেগেটিভ, স্ন্যাপশট

স্কিমা টেস্টিংয়ের কঙ্কাল তিনটি অংশের।

পজিটিভ ফিক্সচার: বাস্তব রেসপন্স থেকে সংগ্রহ করা, যা পাস করা উচিত। মনে করে লিখবেন না — staging থেকে আনুন; হাতে লেখা পজিটিভ শুধু happy path ঢেকে রাখে, null, খালি স্ট্রিং আর শুধু-required সীমা ভুলে যায়।

নেগেটিভ ফিক্সচার: স্কিমার প্রতিটি কনস্ট্রেইন্টের জন্য অন্তত একটি লঙ্ঘনকারী ইনপুট। minimum: 0 হলে -1 (বাতিল) ও 0 (পাস) জোড়া; enum হলে তালিকার বাইরের মান; additionalProperties: false হলে অতিরিক্ত ফিল্ড। নেগেটিভ পজিটিভের চেয়ে বেশি জরুরি: স্কিমা নীরবে খারাপ ডেটা পাস করালে তা সাইলেন্ট বাগ, ডেটাবেস পর্যন্ত দূষিত করে।

ফাইলের নামকে অ্যাসার্শন বানানো নামকরণ রীতি ওপেন সোর্স থেকে কপি করা যায়:

schemas/order.schema.json
fixtures/order/valid-minimal.json        # পাস করতেই হবে
fixtures/order/valid-full.json           # পাস করতেই হবে
fixtures/order/invalid-type.json         # বাতিল হতেই হবে
fixtures/order/invalid-extra-field.json  # বাতিল হতেই হবে

Node-এর বিল্ট-ইন node:test-ই যথেষ্ট:

import { test } from 'node:test';
import { readFileSync, readdirSync } from 'node:fs';
import assert from 'node:assert/strict';
import Ajv2020 from 'ajv/dist/2020.js';
import addFormats from 'ajv-formats';

const ajv = new Ajv2020({ allErrors: true });
addFormats(ajv);
const validate = ajv.compile(
  JSON.parse(readFileSync('schemas/order.schema.json', 'utf8'))
);

for (const f of readdirSync('fixtures/order')) {
  const data = JSON.parse(readFileSync(`fixtures/order/${f}`, 'utf8'));
  if (f.startsWith('valid-')) {
    test(`${f} পাস করা উচিত`, () => { assert.ok(validate(data), JSON.stringify(validate.errors)); });
  } else {
    test(`${f} বাতিল হওয়া উচিত`, () => { assert.ok(!validate(data)); });
  }
}

স্ন্যাপশট: validate.errors সিরিয়ালাইজ করে ফাইলে রেখে তুলনা করুন। কেউ অনিচ্ছাকৃতভাবে কনস্ট্রেইন্ট বদলালে — স্ন্যাপশট সঙ্গে সঙ্গে লাল। Jest/Vitest-এ এক লাইন: expect(errors).toMatchSnapshot(); CI-তে সবসময় --ci দিন: নতুন স্ন্যাপশট নীরবে তৈরি হওয়ার বদলে ফেল করবে, ড্রিফট চুপচাপ গৃহীত হবে না।

৩. একবারেই সব এরর জমা করুন

নেগেটিভ টেস্টে প্রায়ই ভুল হওয়া কনফিগ: ডিফল্টে ভ্যালিডেটর প্রথম এররেই থামে। ডেটা তিন জায়গায় ভাঙা, আপনি দেখছেন একটা, ঠিক করে আবার চালান — এভাবে চলতে থাকে। allErrors: true (Ajv) বা iter_errors (Python) দিলে একবারেই সব দেখা যায়:

from jsonschema import Draft202012Validator

validator = Draft202012Validator(schema)
errors = sorted(validator.iter_errors(bad), key=lambda e: list(e.absolute_path))
for e in errors:
    print(list(e.absolute_path), e.validator, e.message)

Python-এর একটি প্রতি-স্বজ্ঞ তথ্যও আছে: validate() কোন এরর জানাবে তা "প্রথমটা" নয়, best_match যেটা বাছে। আমাদের পরিমাপে তিনটি লঙ্ঘনের মধ্যে (id-র ভুল টাইপ, তালিকার বাইরে role, অতিরিক্ত extra ফিল্ড) শুধু additionalProperties জানানো হয়েছিল — id-র টাইপ এরর লুকানো রইল। CI-তে সম্পূর্ণ দৃশ্যের জন্য সবসময় iter_errors ব্যবহার করুন।

আরও সহজ ফাঁদ: format ডিফল্টে অ্যাসার্শন নয়। ২০২০-১২ ড্রাফটে format অ্যানোটেশন হিসেবে চিহ্নিত: Python-এর jsonschema format_checker পাস না করলে তা ভ্যালিডেটই করে না — আমাদের টেস্টে "not-an-email" মুক্তভাবে পাস করেছে। Ajv-তে ajv-formats ইনস্টল ও রেজিস্টার করতে হয়। ফরম্যাট গুরুত্বপূর্ণ হলে (ইমেইল, তারিখ, uuid) এমন একটি নেগেটিভ ফিক্সচার থাকতেই হবে যা প্রমাণ করে আপনার ভ্যালিডেটর কনফিগ সত্যিই তা যাচাই করছে।

৪. একবার কম্পাইল করুন — কম্পাইলারকে একবারব্যবহারযোগ্য জিনিস ভাববেন না

লোকাল পরিমাপ (Node v22.13.1, Ajv 8.17, একই ত্রিস্তরীয় নেস্টেড স্কিমা, একই লঙ্ঘনকারী ডেটা):

পন্থা ১০,০০০ ভ্যালিডেশনে সময়
প্রতিবার new Ajv() করে compile 42233 ms
মডিউল-লেভেল ইনস্ট্যান্স + একবার compile 6.9 ms

৬০০০ গুণ পার্থক্য। রিকোয়েস্ট হ্যান্ডলারের ভেতরে ভ্যালিডেটর রেখে প্রতি কলে নতুন Ajv বানানো সবচেয়ে প্রচলিত পারফরম্যান্স দুর্ঘটনা। একটি প্রচলিত ভুল ধারণাও নথিভুক্ত করি: "ajv.validate(schema, data) প্রতিবার নতুন করে কম্পাইল করে"। পরিমাপে: ১০০,০০০ কলে 8.2 ms — একবার কম্পাইল করা ফাংশনের 11.2 ms-এর সঙ্গে তুলনীয়, কারণ Ajv-র ভেতরে স্কিমা ক্যাশ আছে। আসল রোগ ক্যাশ মিস নয়, ইনস্ট্যান্স পুনর্ব্যবহার না করা। Python-এও তাই: পুনর্ব্যবহৃত Draft202012Validator প্রতিবার validate() কলের চেয়ে প্রায় ৫০০০ গুণ দ্রুত (২০০০ ইটারেশন: 0.3 ms বনাম 1566 ms), কারণ পরেরটি প্রতি কলে স্কিমা নিজেই পরীক্ষা করে।

৫. গেটগুলো CI-তে ঢেলে দিন: চার দরজা

সস্তা থেকে দামি ক্রমে: সামনেরগুলো পড়লে পিছনেরগুলো চালানোর দরকার নেই:

গেট কমান্ড রূপ কী ধরে খরচ
স্কিমা লিন্ট node scripts/lint-schemas.mjs সিনট্যাক্স এরর, অপ্রচলিত কীওয়ার্ড মিলিসেকেন্ড
ফিক্সচার ভ্যালিডেশন সেকশন ২-এর টেস্ট স্যুট ফিক্সচার-স্কিমা অসঙ্গতি, ভুলে বদলানো কনস্ট্রেইন্ট মিলিসেকেন্ড
ভাঙা পরিবর্তনের diff json-schema-diff-validator main-এর সঙ্গে required মুছে ফেলা, enum সংকুচিত করা, pattern কঠোর করা সেকেন্ড
লাইভ রেসপন্স যাচাই staging-এ smoke টেস্ট, .toMatchSchema() অ্যাসার্শন সত্যিকারের এন্ডপয়েন্টের ড্রিফট সেকেন্ড

GitHub Actions-এর ন্যূনতম কনফিগ:

- run: npm ci
- run: npm run lint:schemas        # Ajv2020 lint
- run: npm run test:schema         # ফিক্সচার ত্রয়ী
- run: npx jest --ci --testPathPattern=schema   # স্ন্যাপশট ড্রিফট রক্ষী

ভাঙা পরিবর্তনের গেট আলাদা জোর দাবি করে: json-schema-diff-validator জাতীয় টুল "required ফিল্ড মুছে গেছে" আর "enum থেকে মান হারিয়েছে" রিভিউতেই লাল করে দেয় — সাধারণ API-diff যে দুটো পরিবর্তনকে যত্ন করে "নন-ব্রেকিং" বলে চালিয়ে দেয়। সহনশীল কনজিউমারের জন্য রেসপন্স ফিল্ড মোছা ক্ষতিকর নয়, কিন্তু exhaustive switch-ওয়ালা কনজিউমারের জন্য প্রোডাকশন ক্র্যাশ।

৬. শুধু বুলিয়ান নয়, এরর অবজেক্ট অ্যাসার্ট করুন

শুধু validate(data) === false যাচাই করা নেগেটিভ টেস্ট যথেষ্ট নয়। একই স্কিমার জন্য নিশ্চিত হোন সে সঠিক কারণে বাতিল করছে: অসম্পর্কিত কনস্ট্রেইন্ট কাকতালীয়ভাবে পাস করে থাকতে পারে। Ajv-র এরর অবজেক্টের একটি বিখ্যাত অসামঞ্জস্য: required ব্যর্থ হলে instancePath প্যারেন্ট অবজেক্টকে দেখায়, আর অনুপস্থিত ফিল্ডের নাম লুকিয়ে থাকে params.missingProperty-তে। id অনুপস্থিত অবস্থায় আমাদের পরিমাপ:

{ "instancePath": "", "schemaPath": "#/required",
  "keyword": "required", "params": { "missingProperty": "id" } }

অ্যাসার্শন লিখুন "keyword=required ও missingProperty=id সহ একটি এরর বিদ্যমান", অস্পষ্ট errors.length > 0 নয়। এরর ফিল্ডের অর্থের পূর্ণ ব্যাখ্যা: JSON Schema এরর বোঝা যায় না: Ajv এরর লোকেট ও মেসেজ গোছানো।

৭. ঘন ঘন ফাঁদ — দ্রুত তালিকা

ফাঁদ পরিণতি প্রতিকার
শুধু ২০০ happy path টেস্ট এরর এনভেলপের ড্রিফট কেউ না দেখা এরর রেসপন্সের জন্যও স্কিমা ও ফিক্সচার
জেনারেট করা স্কিমা রিভিউ ছাড়া এক নমুনা থেকে অনুমিত required বাস্তব ডেটা বাতিল করে; দল ভ্যালিডেশন বন্ধ করে দেয় জেনারেশনের পর required/enum মানুষের রিভিউ
সব ভার্সনে এক গ্লোবাল স্কিমা v1/v2 পার্থক্য মুছে যায় স্কিমা এন্ডপয়েন্টের সঙ্গে ভার্সন করুন
ব্যর্থতায় পুরো payload লগে PII ও সিক্রেট CI লগে লিক শুধু JSON Pointer পথ ও ছাঁটা অংশ লিখুন
জমে থাকা enum পণ্যের নামবদল (canceled→cancelled) ভ্যালিডেশন ভাঙে enum পরিবর্তন কম্প্যাটিবিলিটি রিভিউতে
ভ্যালিডেটর ভার্সন পিন করা হয়নি মেশিনভেদে ভিন্ন আচরণ CI-তে ভ্যালিডেটর ভার্সন পিন করুন
অথরাইজেশন টেস্টের বিকল্প ভাবা 403 এরর স্কিমায় মেলে, তবু রোল লজিক ভুল স্কিমা স্ট্রাকচার দেখে; অথরাইজেশন আলাদা টেস্ট
format-এর অর্থ ভুলে যাওয়া আবর্জনা ঠিকানা মুক্তভাবে পাস ajv-formats ইনস্টল / format_checker পাস, নেগেটিভ যোগ

৮. প্রয়োগের চেকলিস্ট

  1. স্কিমা ফাইল ভার্সন কন্ট্রোলে, node scripts/lint-schemas.mjs লিন্টে;
  2. প্রতি স্কিমায় অন্তত ১টি পজিটিভ ফিক্সচার ও প্রতি কনস্ট্রেইন্টে ১টি নেগেটিভ; ফাইলের নামই অ্যাসার্শন;
  3. এরর অবজেক্ট স্ন্যাপশটে; CI-তে --ci দিন যাতে স্ন্যাপশট নিজে থেকে ড্রিফট না করে;
  4. ভ্যালিডেটর ইনস্ট্যান্স মডিউল-লেভেলে পুনর্ব্যবহার — হ্যান্ডলারের ভেতরে new নয়;
  5. ভাঙা পরিবর্তনের diff PR-এ ঝুলান; required/enum পরিবর্তনে বাধ্যতামূলক রিভিউ;
  6. প্রথম PR-ই প্রমাণ করে গেট পড়তে পারে: একটা required মুছুন, লাল দেখুন, ফেরত আনুন।

ভ্যালিডেশনের কড়াকড়ির প্রশ্ন (কখন additionalProperties বন্ধ করবেন) আলাদা বিষয় — দেখুন additionalProperties খোলা রাখলে স্কিমা ভ্যালিডেশন নিরর্থক। এই নিবন্ধ লেখা নিয়মগুলোকে ধারাবাহিকভাবে বিশ্বাসযোগ্য রাখা নিয়ে। ভিত্তি চাইলে JSON Schema বেসিক থেকে ইঞ্জিনিয়ারিং থেকে শুরু করুন।

FAQ

স্কিমা ভ্যালিডেশন আর Pact-স্টাইল কন্ট্রাক্ট টেস্টিংয়ের সম্পর্ক কী?

কাজের বিভাজন। স্কিমা ভ্যালিডেশন উত্তর দেয় "এই ডকুমেন্ট কাঠামোগতভাবে ঠিক কি না" — একমুখী অ্যাসার্শন। কনজিউমার-চালিত কন্ট্রাক্ট টেস্টিং (Pact) উত্তর দেয় "প্রোভাইডার কি এখনো কনজিউমার X-এর সুনির্দিষ্ট প্রয়োজন মেটায়" এবং দ্বিমুখী যাচাই চায়। ছোট দল স্কিমা গেট দিয়ে শুরু করুক, সার্ভিস সংখ্যা বাড়লে Pact নেবে।

কতগুলো নেগেটিভ টেস্ট যথেষ্ট?

প্রতি কনস্ট্রেইন্টে একটি নেগেটিভই নিম্নসীমা: স্কিমায় যত কনস্ট্রেইন্ট (required, type, minimum, enum, pattern, additionalProperties…), তত নেগেটিভ ফিক্সচার, সঙ্গে সীমামানের বাতিল/পাস জোড়া। নেগেটিভ কনস্ট্রেইন্টের সংখ্যার চেয়ে কম হলে কোনো না কোনো কনস্ট্রেইন্ট অরক্ষিত।

কোন স্কিমা পরিবর্তন "ব্রেকিং"?

কনজিউমারের দৃষ্টিতে বিচার করুন: রেসপন্স ফিল্ড মোছা, enum মোছা বা সংকুচিত করা, pattern কঠোর করা, রিকোয়েস্টে নতুন required ফিল্ড — সবই ব্রেকিং; রেসপন্সে ঐচ্ছিক ফিল্ড যোগ সাধারণত নিরাপদ — যতক্ষণ কনজিউমারের ডিকোডার অজানা কী সহ্য করে। json-schema-diff-validator দিয়ে PR ধাপে তুলনা স্বয়ংক্রিয় করুন, ধূসর এলাকা মানুষ সিদ্ধান্ত নেবে।

টেস্ট কি আসল API-তে মারবে নাকি mock-এ?

দুটোই, তবে স্তরে: ফিক্সচার টেস্ট নেটওয়ার্ক স্পর্শ করে না, মিলিসেকেন্ডে চলে, প্রতি PR-এ চলে; লাইভ যাচাই staging-এ মারে, সেকেন্ড নেয়, smoke পাইপলাইনে থাকে, ক্রেডেনশিয়াল না থাকলে skipif দিয়ে বাদ পড়ে। CI-তে শুধু আসল API চালানো দল নেটওয়ার্ক কাঁপুনি ও রেট লিমিটে ক্লান্ত হয়ে শেষে টেস্ট বন্ধ করে দেয়।

তথ্যসূত্র

সম্পর্কিত নিবন্ধ

JSON Schema এরর যখন অর্থহীন লাগে: Ajv এরর লোকেশন ও মেসেজ গভর্নেন্স

ভ্যালিডেটর মেশিন এরর দেয়; ব্যবহারকারী জানতে চায় কোন ইনপুট বক্স ভুল। Ajv errors অ্যারে, instancePath বনাম schemaPath, গ্রুপিং ও লেখার ধরন।

উন্নত~10 মিনিট পড়াআপডেট 2026-10-31

additionalProperties ছাড়া Schema যাচাই কিছুই যাচাই করে না

যাচাই পাস মানে পরিষ্কার ডেটা নয়। Pydantic ডিফল্টভাবে অজানা ফিল্ড উপেক্ষা করে, Zod সেগুলো সরিয়ে দেয়, Ajv ছেড়ে দেয় — ভুল প্যারামিটার ও টাইপো কী নীরবে পার হয়ে যায়। এই নিবন্ধ নীরব গ্রহণের তিনটি রূপ দেখায় এবং সম্পূর্ণ পদ্ধতি দেয়: অবজেক্ট বন্ধ করা, কোয়ার্সন নিয়ন্ত্রণ, প্রতিটি নেস্টিং স্তর শক্ত করা।

Intermediate~8 মিনিট পড়াআপডেট 2026-11-02

JSON Schema: মূল বিষয় থেকে প্রোডাকশন

কি-ওয়ার্ড অনেক, কিন্তু প্রতিদিন ব্যবহৃত হয় কেবল ডজনখানেক। type/properties/required/$ref ঠিক করুন, এরর মেসেজ ভালো সামলান, তাহলে বাস্তবের আশি শতাংশ ঢাকা পড়ে।

মধ্যবর্তী~14 মিনিট পড়াআপডেট 2026-10-30