Skip to content

Commit 9bdcbad

Browse files
committed
readOnly should affect only properties
1 parent b436f05 commit 9bdcbad

File tree

3 files changed

+262
-211
lines changed

3 files changed

+262
-211
lines changed

aiohttp_swagger3/validators.py

Lines changed: 23 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -576,10 +576,11 @@ def validate(self, request: web.Request, raw: bool) -> Dict[str, str]:
576576
return values
577577

578578

579-
def to_integer(schema: Dict) -> Integer:
579+
def to_integer(schema: Dict, is_property: bool) -> Integer:
580+
read_only = schema.get("readOnly", False) if is_property else False
580581
return Integer(
581582
nullable=schema.get("nullable", False),
582-
readOnly=schema.get("readOnly", False),
583+
readOnly=read_only,
583584
minimum=schema.get("minimum"),
584585
maximum=schema.get("maximum"),
585586
exclusiveMinimum=schema.get("exclusiveMinimum", False),
@@ -590,10 +591,11 @@ def to_integer(schema: Dict) -> Integer:
590591
)
591592

592593

593-
def to_number(schema: Dict) -> Number:
594+
def to_number(schema: Dict, is_property: bool) -> Number:
595+
read_only = schema.get("readOnly", False) if is_property else False
594596
return Number(
595597
nullable=schema.get("nullable", False),
596-
readOnly=schema.get("readOnly", False),
598+
readOnly=read_only,
597599
minimum=schema.get("minimum"),
598600
maximum=schema.get("maximum"),
599601
exclusiveMinimum=schema.get("exclusiveMinimum", False),
@@ -604,11 +606,12 @@ def to_number(schema: Dict) -> Number:
604606
)
605607

606608

607-
def to_string(schema: Dict) -> String:
609+
def to_string(schema: Dict, is_property: bool) -> String:
610+
read_only = schema.get("readOnly", False) if is_property else False
608611
return String(
609612
format=schema.get("format"),
610613
nullable=schema.get("nullable", False),
611-
readOnly=schema.get("readOnly", False),
614+
readOnly=read_only,
612615
minLength=schema.get("minLength"),
613616
maxLength=schema.get("maxLength"),
614617
enum=schema.get("enum"),
@@ -617,27 +620,29 @@ def to_string(schema: Dict) -> String:
617620
)
618621

619622

620-
def to_boolean(schema: Dict) -> Boolean:
623+
def to_boolean(schema: Dict, is_property: bool) -> Boolean:
624+
read_only = schema.get("readOnly", False) if is_property else False
621625
return Boolean(
622626
nullable=schema.get("nullable", False),
623-
readOnly=schema.get("readOnly", False),
627+
readOnly=read_only,
624628
default=schema.get("default"),
625629
)
626630

627631

628-
def to_array(schema: Dict) -> Array:
632+
def to_array(schema: Dict, is_property: bool) -> Array:
633+
read_only = schema.get("readOnly", False) if is_property else False
629634
return Array(
630635
nullable=schema.get("nullable", False),
631-
readOnly=schema.get("readOnly", False),
636+
readOnly=read_only,
632637
validator=schema_to_validator(schema["items"]),
633638
uniqueItems=schema.get("uniqueItems", False),
634639
minItems=schema.get("minItems"),
635640
maxItems=schema.get("maxItems"),
636641
)
637642

638643

639-
def to_object(schema: Dict) -> Object:
640-
properties = {k: schema_to_validator(v) for k, v in schema.get("properties", {}).items()}
644+
def to_object(schema: Dict, is_property: bool) -> Object:
645+
properties = {k: schema_to_validator(v, is_property=True) for k, v in schema.get("properties", {}).items()}
641646
raw_additional_properties = schema.get("additionalProperties", True)
642647
if isinstance(raw_additional_properties, dict):
643648
additional_properties = schema_to_validator(raw_additional_properties)
@@ -649,9 +654,10 @@ def to_object(schema: Dict) -> Object:
649654
if getattr(validator, "readOnly", False):
650655
required.discard(name)
651656

657+
read_only = schema.get("readOnly", False) if is_property else False
652658
return Object(
653659
nullable=schema.get("nullable", False),
654-
readOnly=schema.get("readOnly", False),
660+
readOnly=read_only,
655661
properties=properties,
656662
required=required,
657663
minProperties=schema.get("minProperties"),
@@ -670,15 +676,15 @@ def to_object(schema: Dict) -> Object:
670676
}
671677

672678

673-
def _type_to_validator(schema: Dict) -> Validator:
679+
def _type_to_validator(schema: Dict, *, is_property: bool) -> Validator:
674680
if "type" not in schema:
675681
raise KeyError("type is required")
676682
if schema["type"] not in _TYPE_TO_FACTORY:
677683
raise Exception(f"Unknown type '{schema['type']}'")
678-
return _TYPE_TO_FACTORY[schema["type"]](schema)
684+
return _TYPE_TO_FACTORY[schema["type"]](schema, is_property)
679685

680686

681-
def schema_to_validator(schema: Dict) -> Validator:
687+
def schema_to_validator(schema: Dict, *, is_property: bool = False) -> Validator:
682688
if "$ref" in schema:
683689
components = COMPONENTS.get()
684690
if not components:
@@ -688,7 +694,7 @@ def schema_to_validator(schema: Dict) -> Validator:
688694
schema = components[section][obj]
689695

690696
if not any(field in schema for field in ("oneOf", "anyOf", "allOf")):
691-
return _type_to_validator(schema)
697+
return _type_to_validator(schema, is_property=is_property)
692698

693699
if "oneOf" in schema:
694700
cls: Type[Union[OneOf, AnyOf]] = OneOf

tests/test_docs_request_bodies.py

Lines changed: 0 additions & 194 deletions
Original file line numberDiff line numberDiff line change
@@ -1314,200 +1314,6 @@ async def handler(request, body: Dict):
13141314
assert error == {"body": "no handler for application/json"}
13151315

13161316

1317-
async def test_object_read_only_properties_skipped(swagger_docs, aiohttp_client):
1318-
1319-
routes = web.RouteTableDef()
1320-
1321-
@routes.post("/r")
1322-
async def handler(request, body: Dict):
1323-
"""
1324-
---
1325-
requestBody:
1326-
required: true
1327-
content:
1328-
application/json:
1329-
schema:
1330-
type: object
1331-
properties:
1332-
boolean:
1333-
type: boolean
1334-
readOnly: true
1335-
integer:
1336-
type: integer
1337-
readOnly: true
1338-
number:
1339-
type: number
1340-
readOnly: true
1341-
string:
1342-
type: string
1343-
readOnly: true
1344-
array:
1345-
type: array
1346-
items:
1347-
type: string
1348-
readOnly: true
1349-
object:
1350-
type: object
1351-
readOnly: true
1352-
1353-
responses:
1354-
'200':
1355-
description: OK.
1356-
"""
1357-
return web.json_response(body)
1358-
1359-
swagger = swagger_docs()
1360-
swagger.add_routes(routes)
1361-
1362-
client = await aiohttp_client(swagger._app)
1363-
1364-
resp = await client.post("/r", json={})
1365-
assert resp.status == 200
1366-
assert await resp.json() == {}
1367-
1368-
1369-
async def test_object_read_only_properties_passed(swagger_docs, aiohttp_client):
1370-
1371-
routes = web.RouteTableDef()
1372-
1373-
@routes.post("/r")
1374-
async def handler(request, body: Dict):
1375-
"""
1376-
---
1377-
requestBody:
1378-
required: true
1379-
content:
1380-
application/json:
1381-
schema:
1382-
type: object
1383-
properties:
1384-
boolean:
1385-
type: boolean
1386-
readOnly: true
1387-
integer:
1388-
type: integer
1389-
readOnly: true
1390-
number:
1391-
type: number
1392-
readOnly: true
1393-
string:
1394-
type: string
1395-
readOnly: true
1396-
array:
1397-
type: array
1398-
items:
1399-
type: string
1400-
readOnly: true
1401-
object:
1402-
type: object
1403-
readOnly: true
1404-
1405-
responses:
1406-
'200':
1407-
description: OK.
1408-
"""
1409-
return web.json_response(body)
1410-
1411-
swagger = swagger_docs()
1412-
swagger.add_routes(routes)
1413-
1414-
client = await aiohttp_client(swagger._app)
1415-
1416-
resp = await client.post(
1417-
"/r",
1418-
json={
1419-
"boolean": True,
1420-
"integer": 10,
1421-
"number": 1.1,
1422-
"string": "str",
1423-
"array": [1, 2, 3],
1424-
"object": {"some": "thing"},
1425-
},
1426-
)
1427-
assert resp.status == 400
1428-
error = error_to_json(await resp.text())
1429-
msg = "property is read-only"
1430-
assert error == {
1431-
"body": {
1432-
"boolean": msg,
1433-
"integer": msg,
1434-
"number": msg,
1435-
"string": msg,
1436-
"array": msg,
1437-
"object": msg,
1438-
}
1439-
}
1440-
1441-
1442-
async def test_object_required_read_only_properties(swagger_docs, aiohttp_client):
1443-
1444-
routes = web.RouteTableDef()
1445-
1446-
@routes.post("/r")
1447-
async def handler(request, body: Dict):
1448-
"""
1449-
---
1450-
requestBody:
1451-
required: true
1452-
content:
1453-
application/json:
1454-
schema:
1455-
type: object
1456-
required:
1457-
- id
1458-
- name
1459-
- description
1460-
- updated_at
1461-
properties:
1462-
id:
1463-
type: integer
1464-
readOnly: true
1465-
name:
1466-
type: string
1467-
description:
1468-
type: string
1469-
updated_at:
1470-
type: integer
1471-
readOnly: true
1472-
1473-
responses:
1474-
'200':
1475-
description: OK.
1476-
"""
1477-
return web.json_response(body)
1478-
1479-
swagger = swagger_docs()
1480-
swagger.add_routes(routes)
1481-
1482-
client = await aiohttp_client(swagger._app)
1483-
1484-
body = {
1485-
"name": "name1",
1486-
"description": "description",
1487-
}
1488-
resp = await client.post("/r", json=body)
1489-
assert resp.status == 200
1490-
assert await resp.json() == body
1491-
1492-
resp = await client.post(
1493-
"/r",
1494-
json={
1495-
"id": 1,
1496-
"name": "name1",
1497-
"description": "description",
1498-
"updated_at": 1609407277,
1499-
},
1500-
)
1501-
assert resp.status == 400
1502-
error = error_to_json(await resp.text())
1503-
assert error == {
1504-
"body": {
1505-
"id": "property is read-only",
1506-
"updated_at": "property is read-only",
1507-
}
1508-
}
1509-
1510-
15111317
async def test_nullable_ref(swagger_docs_with_components, aiohttp_client):
15121318

15131319
routes = web.RouteTableDef()

0 commit comments

Comments
 (0)