ЁЯПл The SchoolтА║ЁЯПв APIsтА║ЁЯУЛ рдзрдбрд╛ 04 тАФ JSON рдЖрдгрд┐ schemas: рдкреНрд░рдорд╛рдгрд┐рдд рдлреЙрд░реНрдо
ЁЯЦ╝я╕П See the drawing + lab ЁЯПа Course home ЁЯМ┐ Branch on GitHub тЬПя╕П View source
ЁЯЦ╝я╕П рдЖрдХреГрддреА рдЖрдгрд┐ labThe drawing + lab рдкреВрд░реНрдг рдкрд╛рдирд╛рд╡рд░ рдЙрдШрдбрд╛ тЖЧOpen full page тЖЧ

ЁЯУЛ рдзрдбрд╛ 04 тАФ JSON рдЖрдгрд┐ schemas: рдкреНрд░рдорд╛рдгрд┐рдд рдлреЙрд░реНрдо

ЁЯУН рддреБрдореНрд╣реА рдЗрдереЗ рдЖрд╣рд╛рдд: 12 рдкреИрдХреА рдзрдбрд╛ 04 ┬╖ рдорд╛рдЧреЗ: lesson-03-rest-resources ┬╖ рдкреБрдвреЗ: lesson-05-build-an-api


ЁЯУж рдпрд╛ рдмреНрд░рдБрдЪрдордзреНрдпреЗ рдХрд╛рдп рдЖрд╣реЗ

рдзрдбреЗ 01тАУ03, рдЖрдгрд┐ рддреНрдпрд╛рд╕реЛрдмрдд рдкреНрд░рддреНрдпрдХреНрд╖ form: JSON bodies, рд╡рд╛рдИрдЯ forms рдХрд╛рд░рдгрд╛рд╕рд╣ рдирд╛рдХрд╛рд░рдгрд╛рд░реЗ validation, рдЖрдгрд┐ OpenAPI тАФ counter рд╕реНрд╡реАрдХрд╛рд░рддреЛ рддреНрдпрд╛ рдкреНрд░рддреНрдпреЗрдХ form рдЪрд╛ рдЫрд╛рдкрд▓реЗрд▓рд╛ рдХреЕрдЯрд▓реЙрдЧ. рдЦрд░реА file:

ЁЯзТ 5 рд╡рд░реНрд╖рд╛рдВрдЪреНрдпрд╛ рдореБрд▓рд╛рд▓рд╛ рд╕рдордЬрд╛рд╡рд▓реНрдпрд╛рд╕рд╛рд░рдЦреЗ

Counter рдЦрд░рдбрд▓реЗрд▓реНрдпрд╛ рдЪрд┐рдареНрдареНрдпрд╛ рд╕реНрд╡реАрдХрд╛рд░рдд рдирд╛рд╣реА. рд╡рд┐рджреНрдпрд╛рд░реНрдереНрдпрд╛рдЪреЗ рдирд╛рд╡ рдиреЛрдВрджрд╡рд╛рдпрдЪреЗ рддрд░ рддреБрдореНрд╣реА рдкреНрд░рдорд╛рдгрд┐рдд form ЁЯУЛ рднрд░рддрд╛:

{"name": "Zoya", "class": "3B", "grade": "A"}

Archive рдХрдбреЗ рдЬрд╛рдгреНрдпрд╛рдЪреНрдпрд╛ рдЖрдзреА clerk рддреЛ рддрдкрд╛рд╕рддреЛ: name рднрд░рд▓реЗ рдЖрд╣реЗ рдХрд╛? class рдЖрдкрд▓реНрдпрд╛рдХрдбреЗ рдЦрд░реЛрдЦрд░ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ рд╡рд░реНрдЧрд╛рдВрдкреИрдХреА рдПрдХ рдЖрд╣реЗ рдХрд╛ (3A, 3B)? grade рдЕрд╕реНрддрд┐рддреНрд╡рд╛рдд рдЕрд╕рд▓реЗрд▓реНрдпрд╛ grades рдкреИрдХреА рдПрдХ рдЖрд╣реЗ рдХрд╛? рдирд╕реЗрд▓ рддрд░ slip рд▓рдЧреЗрдЪ 400 рд╢рд┐рдХреНрдХрд╛ рдЖрдгрд┐ рдиреЗрдордХреЗ рдХрд╛рдп рдЪреБрдХрд▓реЗ рдпрд╛рдЪреА field-рд╡рд╛рд░ рдпрд╛рджреА рдШреЗрдКрди рдкрд░рдд рдпреЗрддреЗ тАФ рдЦрд╛рдВрджреЗ рдЙрдбрд╡рдгреЗ рдирд╛рд╣реА, 500 рдирд╛рд╣реА, "рдХрд╛рд╣реАрддрд░реА рдЪреБрдХрд▓реЗ" рдирд╛рд╣реА.

рдЖрдгрд┐ forms рдХрд╕реЗ рджрд┐рд╕рддрд╛рдд рдпрд╛рдЪрд╛ рдХреЛрдгрд╛рд▓рд╛рдЪ рдЕрдВрджрд╛рдЬ рд▓рд╛рд╡рд╛рд╡рд╛ рд▓рд╛рдЧреВ рдирдпреЗ рдореНрд╣рдгреВрди office рддреНрдпрд╛рдВрдЪрд╛ рдХреЕрдЯрд▓реЙрдЧ рдЫрд╛рдкрддреЗ тАФ рдкреНрд░рддреНрдпреЗрдХ path, рдкреНрд░рддреНрдпреЗрдХ parameter, рдкреНрд░рддреНрдпреЗрдХ field, рдкреНрд░рддреНрдпреЗрдХ рд╢рд┐рдХреНрдХрд╛ тАФ OpenAPI рдирд╛рд╡рд╛рдЪреНрдпрд╛ рдкреНрд░рдорд╛рдгрд┐рдд рд░рдЪрдиреЗрдд. рдорд╛рдгрд╕реЗ рддреЛ рд╡рд╛рдЪрддрд╛рдд; рд╕рд╛рдзрдиреЗ рддреНрдпрд╛рддреВрди documentation, client libraries рдЖрдгрд┐ tests рддрдпрд╛рд░ рдХрд░рддрд╛рдд.

ЁЯЧ║я╕П рдЖрдХреГрддреА

flowchart LR
    form["ЁЯУЛ the form (JSON)<br/>name ┬╖ class тИИ {3A,3B} ┬╖ grade?"]
    val["тЬЕ validate()<br/>required? type? allowed value?"]
    ok["201 Created<br/>Location: /v1/students/6"]
    bad["400 validation_failed<br/>problems: [{field, problem}, тАж]"]
    form -->|"1"| val
    val -->|"2 fine"| ok
    val -.->|"2 not fine"| bad
    cat["ЁЯУЪ openapi.yaml тАФ the catalogue<br/>paths ┬╖ params ┬╖ schemas ┬╖ errors"]
    cat -.->|"3 describes all of it"| form

тЭУ рдХрд╛рдп

ЁЯдФ рдХрд╛

рдХрд╛рд░рдг archive рд╣рд╛ рдорд╣рд╛рдЧрдбрд╛ рднрд╛рдЧ рдЖрд╣реЗ. Counter рд╡рд░ рддрдкрд╛рд╕рд▓реЗрд▓реНрдпрд╛ form рд▓рд╛ microseconds рд▓рд╛рдЧрддрд╛рдд; database рдордзреНрдпреЗ рд▓рд┐рд╣рд┐рд▓реЗрд▓реНрдпрд╛ рд╡рд╛рдИрдЯ row рдореБрд│реЗ data рдЪреА рд╕рд╛рдлрд╕рдлрд╛рдИ рдХрд░рд╛рд╡реА рд▓рд╛рдЧрддреЗ, рдЖрдгрд┐ рдЬрд┐рдереЗ 400 рд╣рд╡рд╛ рддрд┐рдереЗ 500 рджрд┐рд▓реНрдпрд╛рдиреЗ on-call engineer рдЪрд╛ рдПрдХ рддрд╛рд╕ рдЬрд╛рддреЛ. рдХреЕрдЯрд▓реЙрдЧ рд╣рд╛ рджреБрд╕рд░рд╛ рдЕрд░реНрдзрд╛ рднрд╛рдЧ рдЖрд╣реЗ: рд▓рд┐рдЦрд┐рдд contract рдирд╕рд▓реЗрд▓реЗ API рдореНрд╣рдгрдЬреЗ рдлрдХреНрдд рдЕрдлрд╡рд╛.

ЁЯФз рдХрд╕реЗ (рдпрд╛ repo рдордзреНрдпреЗ)

school_api.py рдордзрд▓реЗ validate_student(body) {field, problem} dicts рдЪреА рдпрд╛рджреА рдкрд░рдд рджреЗрддреЗ; рдпрд╛рджреА рд░рд┐рдХрд╛рдореА рдирд╕реЗрд▓ рддреЗрд╡реНрд╣рд╛ do_POST рдЖрдгрд┐ do_PUT 400 validation_failed рдиреЗ рдирд╛рдХрд╛рд░рддрд╛рдд. рддреНрдпрд╛рдЪреА рдУрд│-рдУрд│ рддреБрд▓рдирд╛ api/openapi.yaml рдордзрд▓реНрдпрд╛ StudentInput рд╢реА рдХрд░рд╛ тАФ рддреЗ рдиреЗрд╣рдореА рдЬреБрд│рд▓реЗрдЪ рдкрд╛рд╣рд┐рдЬреЗрдд.

ЁЯзк рдХрд░реВрди рдкрд╛рд╣рд╛

K='X-API-Key: hall-pass-123'; B=http://127.0.0.1:8080/v1; J='Content-Type: application/json'
curl -s -X POST $B/students -H "$K" -H "$J" -d '{"name":"","class":"9Z"}'        # 400 with two problems
curl -s -X POST $B/students -H "$K" -H "$J" -d '{"name":"Zoya","class":"3B"}'    # 201; grade defaults to C
curl -s -X POST $B/students -H "$K" -H "$J" -d '{"name":"Zoya","class":"3B","grade":"Z"}'   # 400: grade
# now change the contract: add a required "roll_no" field to validate_student() AND openapi.yaml, restart, retest

тЬЕ рддрдкрд╛рд╕рд╛ тАФ рддреБрдореНрд╣рд╛рд▓рд╛ рдХрд╛рдп рджрд┐рд╕рд╛рдпрд▓рд╛ рд╣рд╡реЗ

рдкрд╣рд┐рд▓рд╛ call name рдЖрдгрд┐ class рдпрд╛рдВрдЪреА рдирд╛рд╡реЗ рдЕрд╕рд▓реЗрд▓реНрдпрд╛ problems рд╕рд╣ 400 рдкрд░рдд рджреЗрддреЛ; рджреБрд╕рд░рд╛ 201 рдЖрдгрд┐ "grade": "C" рдЕрд╕рд▓реЗрд▓реА body рджреЗрддреЛ; рддрд┐рд╕рд░рд╛ grade рдЪреЗ рдирд╛рд╡ рд╕рд╛рдВрдЧрддреЛ. рддреБрдордЪрд╛ roll_no рдмрджрд▓ рдХреЗрд▓реНрдпрд╛рдирдВрддрд░, рддреБрдореНрд╣реА рддреЗ field рдЬреЛрдбреЗрдкрд░реНрдпрдВрдд рджреБрд╕рд░рд╛ call рдЕрдпрд╢рд╕реНрд╡реА рд╣реЛрддреЛ тАФ рдЖрдгрд┐ рдХреЕрдЯрд▓реЙрдЧ рдХрд╛ рддреЗ рд╕рд╛рдВрдЧрддреЛ.

ЁЯПБ рддреБрдореНрд╣реА рдЖрддреНрддрд╛рдЪ рдХрд╛рдп рд╕рд┐рджреНрдз рдХреЗрд▓реЗ

рдХреЛрдгрддреЗрд╣реА рдХрд╛рдо рд╣реЛрдгреНрдпрд╛рдЖрдзреАрдЪ counter рд╡рд╛рдИрдЯ forms field-рдкрд╛рддрд│реАрд╡рд░рдЪреНрдпрд╛ рдХрд╛рд░рдгрд╛рд╕рд╣ рдирд╛рдХрд╛рд░рддреЛ, рдЖрдгрд┐ рдХреЕрдЯрд▓реЙрдЧ рд╡ code рдПрдХрд╛рдЪ form рдЪреЗ рд╡рд░реНрдгрди рдХрд░рддрд╛рдд.

тЪая╕П рдиреЗрд╣рдореАрдЪреНрдпрд╛ рдЪреБрдХрд╛

ЁЯПн рдкреНрд░рддреНрдпрдХреНрд╖ рд╡рд╛рдкрд░рд╛рдд рд╣реЗ рдХрд╛ рдорд╣рддреНрддреНрд╡рд╛рдЪреЗ: contract-first teams OpenAPI file рдордзреВрди client SDKs, mocks рдЖрдгрд┐ tests generate рдХрд░рддрд╛рдд, рддреНрдпрд╛рдореБрд│реЗ frontend рдЖрдгрд┐ backend рдПрдХрд╛рдЪ рд╡реЗрд│реА рдмрд╛рдВрдзрд▓реЗ рдЬрд╛рддрд╛рдд. Contract рдирд╕рд▓реЗрд▓реНрдпрд╛ teams рдирд╛ field рдЪреА рдирд╛рд╡реЗ Slack рдордзреНрдпреЗ рдХрд│рддрд╛рдд.

тПня╕П рдкреБрдвреЗ

рдЖрддрд╛ counter рдЙрдШрдбреВрди рдкреНрд░рддреНрдпреЗрдХ рдУрд│ рд╡рд╛рдЪрд╛рдпрдЪреА рд╡реЗрд│: API рдмрд╛рдВрдзрд╛ тАФ school_api.py рдЪреЗ рдЪрд╛рд░ рднрд╛рдЧ.

git checkout lesson-05-build-an-api

ЁЯУЛ Lesson 04 тАФ JSON & schemas: the standard form

ЁЯУН You are here: Lesson 04 of 12 ┬╖ Previous: lesson-03-rest-resources ┬╖ Next: lesson-05-build-an-api


ЁЯУж What's in this branch

Lessons 01тАУ03, plus the form itself: JSON bodies, validation that refuses bad forms with a reason, and OpenAPI тАФ the printed catalogue of every form the counter accepts. Real file:

ЁЯзТ Explain like I'm 5

The counter does not accept scribbled notes. To enrol a student you fill in the standard form ЁЯУЛ:

{"name": "Zoya", "class": "3B", "grade": "A"}

The clerk checks it before walking to the archive: is name filled in? is class one of the classes we actually have (3A, 3B)? is grade one of the grades that exist? If not, the slip comes straight back with a 400 stamp and a list of exactly what is wrong, field by field тАФ not a shrug, not a 500, not "something went wrong".

And so nobody has to guess what the forms look like, the office prints a catalogue of them тАФ every path, every parameter, every field, every stamp тАФ in a standard layout called OpenAPI. Humans read it; tools turn it into documentation, client libraries and tests.

ЁЯЧ║я╕П Diagram

flowchart LR
    form["ЁЯУЛ the form (JSON)<br/>name ┬╖ class тИИ {3A,3B} ┬╖ grade?"]
    val["тЬЕ validate()<br/>required? type? allowed value?"]
    ok["201 Created<br/>Location: /v1/students/6"]
    bad["400 validation_failed<br/>problems: [{field, problem}, тАж]"]
    form -->|"1"| val
    val -->|"2 fine"| ok
    val -.->|"2 not fine"| bad
    cat["ЁЯУЪ openapi.yaml тАФ the catalogue<br/>paths ┬╖ params ┬╖ schemas ┬╖ errors"]
    cat -.->|"3 describes all of it"| form

тЭУ What

ЁЯдФ Why

Because the archive is the expensive part. A form checked at the counter costs microseconds; a bad row written to the database costs a data clean-up, and a 500 where a 400 belonged costs an on-call engineer an hour. The catalogue is the other half: an API without a written contract is a rumour.

ЁЯФз How (in this repo)

validate_student(body) in school_api.py returns a list of {field, problem} dicts; do_POST and do_PUT refuse with 400 validation_failed when the list is not empty. Compare it, line by line, with StudentInput in api/openapi.yaml тАФ they must always agree.

ЁЯзк Try it

K='X-API-Key: hall-pass-123'; B=http://127.0.0.1:8080/v1; J='Content-Type: application/json'
curl -s -X POST $B/students -H "$K" -H "$J" -d '{"name":"","class":"9Z"}'        # 400 with two problems
curl -s -X POST $B/students -H "$K" -H "$J" -d '{"name":"Zoya","class":"3B"}'    # 201; grade defaults to C
curl -s -X POST $B/students -H "$K" -H "$J" -d '{"name":"Zoya","class":"3B","grade":"Z"}'   # 400: grade
# now change the contract: add a required "roll_no" field to validate_student() AND openapi.yaml, restart, retest

тЬЕ Verify тАФ what you should see

The first call returns 400 with problems naming name and class; the second returns 201 and a body with "grade": "C"; the third names grade. After your roll_no change, the second call fails until you add the field тАФ and the catalogue says why.

ЁЯПБ What you just proved

The counter refuses bad forms with a field-level reason before any work is done, and the catalogue and the code describe the same form.

тЪая╕П Common mistakes

ЁЯПн Why this matters in production: contract-first teams generate client SDKs, mocks and tests from the OpenAPI file, so frontend and backend build in parallel. Contract-less teams discover field names in Slack.

тПня╕П Next

Time to open the counter and read every line: build an API тАФ the four parts of school_api.py.

git checkout lesson-05-build-an-api
тЖР Previousrest resourcesNext тЖТbuild an api

This page is the lesson's README from the lesson-04-json-and-schemas branch, shown here so the whole School stays on one site. Code files open on GitHub at the same branch.