Skip to content

Commit dda30e1

Browse files
authored
Merge pull request #724 from NHSDigital/feature/eema1-NRL-474-authorBasicValidation
NRL-474 validate author system, length and value format
2 parents 2cbeb7f + 16f50c6 commit dda30e1

File tree

12 files changed

+333
-8
lines changed

12 files changed

+333
-8
lines changed

api/producer/createDocumentReference/tests/test_create_document_reference.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,21 @@ def test_create_document_reference_invalid_body():
238238
"diagnostics": "Request body could not be parsed (status: Field required)",
239239
"expression": ["status"],
240240
},
241+
{
242+
"severity": "error",
243+
"code": "invalid",
244+
"details": {
245+
"coding": [
246+
{
247+
"code": "MESSAGE_NOT_WELL_FORMED",
248+
"display": "Message not well formed",
249+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
250+
}
251+
],
252+
},
253+
"diagnostics": "Request body could not be parsed (author: Field required)",
254+
"expression": ["author"],
255+
},
241256
{
242257
"severity": "error",
243258
"code": "invalid",

api/producer/swagger.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1383,6 +1383,7 @@ components:
13831383
- resourceType
13841384
- status
13851385
- content
1386+
- author
13861387
Bundle:
13871388
type: object
13881389
properties:

api/producer/updateDocumentReference/tests/test_update_document_reference.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -246,6 +246,21 @@ def test_create_document_reference_invalid_body():
246246
"diagnostics": "Request body could not be parsed (status: Field required)",
247247
"expression": ["status"],
248248
},
249+
{
250+
"severity": "error",
251+
"code": "invalid",
252+
"details": {
253+
"coding": [
254+
{
255+
"code": "MESSAGE_NOT_WELL_FORMED",
256+
"display": "Message not well formed",
257+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
258+
}
259+
],
260+
},
261+
"diagnostics": "Request body could not be parsed (author: Field required)",
262+
"expression": ["author"],
263+
},
249264
{
250265
"severity": "error",
251266
"code": "invalid",

api/producer/upsertDocumentReference/tests/test_upsert_document_reference.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,21 @@ def test_upsert_document_reference_invalid_body():
323323
"diagnostics": "Request body could not be parsed (status: Field required)",
324324
"expression": ["status"],
325325
},
326+
{
327+
"severity": "error",
328+
"code": "invalid",
329+
"details": {
330+
"coding": [
331+
{
332+
"code": "MESSAGE_NOT_WELL_FORMED",
333+
"display": "Message not well formed",
334+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
335+
}
336+
],
337+
},
338+
"diagnostics": "Request body could not be parsed (author: Field required)",
339+
"expression": ["author"],
340+
},
326341
{
327342
"severity": "error",
328343
"code": "invalid",

layer/nrlf/consumer/fhir/r4/model.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: swagger.yaml
3-
# timestamp: 2024-10-28T10:30:48+00:00
3+
# timestamp: 2024-11-04T11:43:16+00:00
44

55
from __future__ import annotations
66

layer/nrlf/core/constants.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,15 @@ class Source(Enum):
88

99
VALID_SOURCES = frozenset(item.value for item in Source.__members__.values())
1010
EMPTY_VALUES = ("", None, [], {})
11-
REQUIRED_CREATE_FIELDS = ["custodian", "id", "type", "status", "subject", "category"]
11+
REQUIRED_CREATE_FIELDS = [
12+
"custodian",
13+
"id",
14+
"type",
15+
"status",
16+
"subject",
17+
"category",
18+
"author",
19+
]
1220
JSON_TYPES = {dict, list}
1321
NHS_NUMBER_INDEX = "idx_nhs_number_by_id"
1422
ID_SEPARATOR = "-"

layer/nrlf/core/tests/test_request.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,23 @@ def test_parse_body_invalid_json():
274274
"diagnostics": "Request body could not be parsed (type: Input should be an object)",
275275
"expression": ["type"],
276276
},
277+
{
278+
"code": "invalid",
279+
"details": {
280+
"coding": [
281+
{
282+
"code": "MESSAGE_NOT_WELL_FORMED",
283+
"display": "Message not well formed",
284+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
285+
},
286+
],
287+
},
288+
"diagnostics": "Request body could not be parsed (author: Field required)",
289+
"expression": [
290+
"author",
291+
],
292+
"severity": "error",
293+
},
277294
{
278295
"severity": "error",
279296
"code": "invalid",

layer/nrlf/core/tests/test_validators.py

Lines changed: 135 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import pytest
44

5-
from nrlf.core.constants import PointerTypes
5+
from nrlf.core.constants import ODS_SYSTEM, PointerTypes
66
from nrlf.core.errors import ParseError
77
from nrlf.core.validators import (
88
DocumentReferenceValidator,
@@ -640,6 +640,140 @@ def test_validate_content_extension_too_many_extensions():
640640
}
641641

642642

643+
def test_validate_author_too_many_authors():
644+
validator = DocumentReferenceValidator()
645+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
646+
647+
document_ref_data["author"].append(
648+
{
649+
"identifier": {
650+
"system": ODS_SYSTEM,
651+
"value": "someODSCode",
652+
}
653+
}
654+
)
655+
656+
result = validator.validate(document_ref_data)
657+
658+
assert result.is_valid is False
659+
assert result.resource.id == "Y05868-99999-99999-999999"
660+
assert len(result.issues) == 1
661+
assert result.issues[0].model_dump(exclude_none=True) == {
662+
"severity": "error",
663+
"code": "invalid",
664+
"details": {
665+
"coding": [
666+
{
667+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
668+
"code": "INVALID_RESOURCE",
669+
"display": "Invalid validation of resource",
670+
}
671+
]
672+
},
673+
"diagnostics": "Invalid author length: 2 Author must only contain a single value",
674+
"expression": ["author"],
675+
}
676+
677+
678+
def test_validate_author_system_invalid():
679+
validator = DocumentReferenceValidator()
680+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
681+
682+
document_ref_data["author"][0] = {
683+
"identifier": {
684+
"system": "some system",
685+
"value": "someODSCode",
686+
}
687+
}
688+
689+
result = validator.validate(document_ref_data)
690+
691+
assert result.is_valid is False
692+
assert result.resource.id == "Y05868-99999-99999-999999"
693+
assert len(result.issues) == 1
694+
assert result.issues[0].model_dump(exclude_none=True) == {
695+
"severity": "error",
696+
"code": "invalid",
697+
"details": {
698+
"coding": [
699+
{
700+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
701+
"code": "INVALID_IDENTIFIER_SYSTEM",
702+
"display": "Invalid identifier system",
703+
}
704+
]
705+
},
706+
"diagnostics": f"Invalid author system: 'some system' Author system must be 'https://fhir.nhs.uk/Id/ods-organization-code'",
707+
"expression": ["author[0].identifier.system"],
708+
}
709+
710+
711+
def test_validate_author_value_invalid():
712+
validator = DocumentReferenceValidator()
713+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
714+
715+
document_ref_data["author"][0] = {
716+
"identifier": {
717+
"system": ODS_SYSTEM,
718+
"value": "!!!!!!12sd",
719+
}
720+
}
721+
722+
result = validator.validate(document_ref_data)
723+
724+
assert result.is_valid is False
725+
assert result.resource.id == "Y05868-99999-99999-999999"
726+
assert len(result.issues) == 1
727+
assert result.issues[0].model_dump(exclude_none=True) == {
728+
"severity": "error",
729+
"code": "value",
730+
"details": {
731+
"coding": [
732+
{
733+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
734+
"code": "INVALID_RESOURCE",
735+
"display": "Invalid validation of resource",
736+
}
737+
]
738+
},
739+
"diagnostics": f"Invalid author value: '!!!!!!12sd' Author value must be alphanumeric",
740+
"expression": ["author[0].identifier.value"],
741+
}
742+
743+
744+
def test_validate_author_value_too_long():
745+
validator = DocumentReferenceValidator()
746+
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")
747+
748+
document_ref_data["author"][0] = {
749+
"identifier": {
750+
"system": ODS_SYSTEM,
751+
"value": "d1111111111111111111111111111111111111111111111",
752+
}
753+
}
754+
755+
result = validator.validate(document_ref_data)
756+
757+
assert result.is_valid is False
758+
assert result.resource.id == "Y05868-99999-99999-999999"
759+
assert len(result.issues) == 1
760+
assert result.issues[0].model_dump(exclude_none=True) == {
761+
"severity": "error",
762+
"code": "value",
763+
"details": {
764+
"coding": [
765+
{
766+
"system": "https://fhir.nhs.uk/ValueSet/Spine-ErrorOrWarningCode-1",
767+
"code": "INVALID_RESOURCE",
768+
"display": "Invalid validation of resource",
769+
}
770+
]
771+
},
772+
"diagnostics": f"Invalid author value: 'd1111111111111111111111111111111111111111111111' Author value must be less than 13 characters",
773+
"expression": ["author[0].identifier.value"],
774+
}
775+
776+
643777
def test_validate_content_extension_invalid_code():
644778
validator = DocumentReferenceValidator()
645779
document_ref_data = load_document_reference_json("Y05868-736253002-Valid")

layer/nrlf/core/validators.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from pydantic import ValidationError
66

77
from nrlf.core.codes import SpineErrorConcept
8-
from nrlf.core.constants import CATEGORY_ATTRIBUTES, REQUIRED_CREATE_FIELDS
8+
from nrlf.core.constants import CATEGORY_ATTRIBUTES, ODS_SYSTEM, REQUIRED_CREATE_FIELDS
99
from nrlf.core.errors import ParseError
1010
from nrlf.core.logger import LogReference, logger
1111
from nrlf.core.types import DocumentReference, OperationOutcomeIssue, RequestQueryType
@@ -118,6 +118,7 @@ def validate(self, data: Dict[str, Any] | DocumentReference):
118118
self._validate_relates_to(resource)
119119
self._validate_ssp_asid(resource)
120120
self._validate_category(resource)
121+
self._validate_author(resource)
121122
if resource.content[0].extension:
122123
self._validate_content_extension(resource)
123124

@@ -464,3 +465,48 @@ def _validate_content_extension(self, model: DocumentReference):
464465
field=f"content[{i}].extension[0].url",
465466
)
466467
return
468+
469+
def _validate_author(self, model: DocumentReference):
470+
"""
471+
Validate the author field contains an appropriate coding system and code.
472+
"""
473+
logger.log(LogReference.VALIDATOR001, step="author")
474+
475+
if len(model.author) > 1:
476+
self.result.add_error(
477+
issue_code="invalid",
478+
error_code="INVALID_RESOURCE",
479+
diagnostics=f"Invalid author length: {len(model.author)} Author must only contain a single value",
480+
field=f"author",
481+
)
482+
return
483+
484+
logger.debug("Validating author")
485+
identifier = model.author[0].identifier
486+
487+
if identifier.system != ODS_SYSTEM:
488+
self.result.add_error(
489+
issue_code="invalid",
490+
error_code="INVALID_IDENTIFIER_SYSTEM",
491+
diagnostics=f"Invalid author system: '{identifier.system}' Author system must be '{ODS_SYSTEM}'",
492+
field=f"author[0].identifier.system",
493+
)
494+
return
495+
496+
if not identifier.value.isalnum():
497+
self.result.add_error(
498+
issue_code="value",
499+
error_code="INVALID_RESOURCE",
500+
diagnostics=f"Invalid author value: '{identifier.value}' Author value must be alphanumeric",
501+
field=f"author[0].identifier.value",
502+
)
503+
return
504+
505+
if len(identifier.value) > 12:
506+
self.result.add_error(
507+
issue_code="value",
508+
error_code="INVALID_RESOURCE",
509+
diagnostics=f"Invalid author value: '{identifier.value}' Author value must be less than 13 characters",
510+
field=f"author[0].identifier.value",
511+
)
512+
return

layer/nrlf/producer/fhir/r4/model.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# generated by datamodel-codegen:
22
# filename: swagger.yaml
3-
# timestamp: 2024-10-28T10:30:43+00:00
3+
# timestamp: 2024-11-04T11:43:12+00:00
44

55
from __future__ import annotations
66

@@ -582,7 +582,7 @@ class DocumentReference(BaseModel):
582582
pattern="([0-9]([0-9]([0-9][1-9]|[1-9]0)|[1-9]00)|[1-9]000)-(0[1-9]|1[0-2])-(0[1-9]|[1-2][0-9]|3[0-1])T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|(\\+|-)((0[0-9]|1[0-3]):[0-5][0-9]|14:00))",
583583
),
584584
] = None
585-
author: Optional[List[Reference]] = None
585+
author: List[Reference]
586586
authenticator: Annotated[
587587
Optional[Reference],
588588
Field(

0 commit comments

Comments
 (0)