diff --git a/pyx12/error_997.py b/pyx12/error_997.py index 64c6c867..c66055d9 100644 --- a/pyx12/error_997.py +++ b/pyx12/error_997.py @@ -395,10 +395,6 @@ def visit_seg(self, err_seg: Any) -> None: :param err_seg: Segment error handler :type err_seg: L{error_handler.err_seg} """ - # Legacy raw-X12 codes accepted by the AK304 channel today. - # PR 5 will drop this fallback once all producers emit pyx12 codes - # routed through pyx12.error_codes.ERROR_CODES. - valid_AK3_codes = ("1", "2", "3", "4", "5", "6", "7", "8") seg_base = pyx12.segment.Segment("AK3", "~", "*", ":") seg_base.append(err_seg.seg_id) seg_base.append("%i" % err_seg.seg_count) @@ -407,16 +403,17 @@ def visit_seg(self, err_seg: Any) -> None: else: seg_base.append("") seg_str = seg_base.format("~", "*", ":") + # Every err_cde reaching this point is a pyx12 code from + # pyx12.error_codes.ERROR_CODES (PRs #175-#178 migrated all + # producers). None spec or None ak_code means the code does not + # surface in the 4010 AK3 channel (e.g. parser HL1/HL2/LX). emitted: set[str] = set() for err_cde in [x[0] for x in err_seg.errors]: spec = pyx12.error_codes.ERROR_CODES.get(err_cde) - if spec is not None: - ak_code = spec.ak_code - elif err_cde in valid_AK3_codes: - ak_code = err_cde - else: - ak_code = None - if ak_code is None or ak_code in emitted: + if spec is None or spec.ak_code is None: + continue + ak_code = spec.ak_code + if ak_code in emitted: continue seg_data = pyx12.segment.Segment(seg_str, "~", "*", ":") seg_data.set("AK304", ak_code) @@ -432,10 +429,6 @@ def visit_ele(self, err_ele: Any) -> None: :param err_ele: Segment error handler :type err_ele: L{error_handler.err_ele} """ - # Legacy raw-X12 codes accepted by the AK403 channel today. - # PR 5 will drop this fallback once all producers emit pyx12 codes - # routed through pyx12.error_codes.ERROR_CODES. - valid_AK4_codes = ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") seg_base = pyx12.segment.Segment("AK4", "~", "*", ":") if err_ele.subele_pos: seg_base.append("%i:%i" % (err_ele.ele_pos, err_ele.subele_pos)) @@ -446,16 +439,10 @@ def visit_ele(self, err_ele: Any) -> None: seg_str = seg_base.format("~", "*", ":") for err_cde, err_str, bad_value in err_ele.errors: spec = pyx12.error_codes.ERROR_CODES.get(err_cde) - if spec is not None: - ak_code = spec.ak_code - elif err_cde in valid_AK4_codes: - ak_code = err_cde - else: - ak_code = None - if ak_code is None: + if spec is None or spec.ak_code is None: continue seg_data = pyx12.segment.Segment(seg_str, "~", "*", ":") - seg_data.set("AK403", ak_code) + seg_data.set("AK403", spec.ak_code) if bad_value: seg_data.set("AK404", error_visitor.ascii_only(bad_value)) self._write(seg_data) diff --git a/pyx12/error_999.py b/pyx12/error_999.py index de454afc..c06cc62d 100644 --- a/pyx12/error_999.py +++ b/pyx12/error_999.py @@ -325,29 +325,23 @@ def visit_seg(self, err_seg: Any) -> None: :param err_seg: Segment error handler :type err_seg: L{error_handler.err_seg} """ - # Legacy raw-X12 codes accepted by the IK304 channel today. - # PR 5 will drop this fallback once all producers emit pyx12 codes - # routed through pyx12.error_codes.ERROR_CODES. - valid_IK3_codes = ("1", "2", "3", "4", "5", "6", "7", "8", "I4", "I6", "I7", "I8", "I9") seg_base = pyx12.segment.Segment("IK3", "~", "*", ":") seg_base.set("01", err_seg.seg_id) seg_base.set("02", "%i" % err_seg.seg_count) if err_seg.ls_id: seg_base.set("03", err_seg.ls_id) seg_str = seg_base.format("~", "*", ":") - # Resolve each err_cde to its IK3-04 code via the pyx12.error_codes - # table. None means "drop" (e.g. parser HL1/HL2/LX). Falls through - # to legacy raw-X12 lookup for codes not in the table. + # Every err_cde reaching this point is a pyx12 code from + # pyx12.error_codes.ERROR_CODES (PRs #175-#178 migrated all + # producers). None spec or None ik_code means the code does not + # surface in the 5010 IK3 channel (e.g. parser HL1/HL2/LX). emitted: set[str] = set() for err_cde in [x[0] for x in err_seg.errors]: spec = pyx12.error_codes.ERROR_CODES.get(err_cde) - if spec is not None: - ik_code = spec.ik_code - elif err_cde in valid_IK3_codes: - ik_code = err_cde - else: - ik_code = None - if ik_code is None or ik_code in emitted: + if spec is None or spec.ik_code is None: + continue + ik_code = spec.ik_code + if ik_code in emitted: continue seg_data = pyx12.segment.Segment(seg_str, "~", "*", ":") seg_data.set("IK304", ik_code) @@ -363,29 +357,6 @@ def visit_ele(self, err_ele: Any) -> None: :param err_ele: Segment error handler :type err_ele: L{error_handler.err_ele} """ - # Legacy raw-X12 codes accepted by the IK403 channel today. - # PR 5 will drop this fallback once all producers emit pyx12 codes - # routed through pyx12.error_codes.ERROR_CODES. - valid_IK4_codes = ( - "1", - "2", - "3", - "4", - "5", - "6", - "7", - "8", - "9", - "10", - "12", - "13", - "I10", - "I11", - "I12", - "I13", - "I6", - "I9", - ) seg_base = pyx12.segment.Segment("IK4", "~", "*", ":") seg_base.set("01-1", "%i" % (err_ele.ele_pos)) if err_ele.subele_pos: @@ -397,16 +368,10 @@ def visit_ele(self, err_ele: Any) -> None: seg_str = seg_base.format("~", "*", ":") for err_cde, err_str, bad_value in err_ele.errors: spec = pyx12.error_codes.ERROR_CODES.get(err_cde) - if spec is not None: - ik_code = spec.ik_code - elif err_cde in valid_IK4_codes: - ik_code = err_cde - else: - ik_code = None - if ik_code is None: + if spec is None or spec.ik_code is None: continue seg_data = pyx12.segment.Segment(seg_str, "~", "*", ":") - seg_data.set("IK403", ik_code) + seg_data.set("IK403", spec.ik_code) if bad_value: seg_data.set("IK404", pyx12.error_visitor.ascii_only(bad_value)) self.wr.Write(seg_data) diff --git a/pyx12/error_item.py b/pyx12/error_item.py index 55b12446..188e681f 100644 --- a/pyx12/error_item.py +++ b/pyx12/error_item.py @@ -52,8 +52,6 @@ "030", "031", ) -seg_errors = ("1", "2", "3", "4", "5", "6", "7", "8") -ele_errors = ("1", "2", "3", "4", "5", "6", "7", "8", "9", "10") @dataclass(slots=True, frozen=True) @@ -84,12 +82,14 @@ class SegError(ErrorItem): ls_id: str | None = None def __post_init__(self) -> None: - # During the PR 2-4 producer migration, accept either legacy - # raw-X12 codes ("1"-"8") or new pyx12 codes from ERROR_CODES. - # PR 5 narrows this to ERROR_CODES only once every producer has - # migrated. - if self.err_cde not in seg_errors and self.err_cde not in ERROR_CODES: - raise EngineError('Invalid segment level error code "%s"' % (self.err_cde)) + # PR 5: producers all emit pyx12 codes from ERROR_CODES. The + # legacy raw-X12 fallback that accepted "1"-"8" is gone; an + # unknown err_cde here is now a hard error. + if self.err_cde not in ERROR_CODES: + raise EngineError( + 'Unknown pyx12 error code "%s" (not in pyx12.error_codes.ERROR_CODES)' + % (self.err_cde) + ) @dataclass(slots=True, frozen=True) @@ -103,9 +103,11 @@ class EleError(ErrorItem): map_node: Any = None def __post_init__(self) -> None: - # During the PR 2-4 producer migration, accept either legacy - # raw-X12 codes ("1"-"10") or new pyx12 codes from ERROR_CODES. - # PR 5 narrows this to ERROR_CODES only once every producer has - # migrated. - if self.err_cde not in ele_errors and self.err_cde not in ERROR_CODES: - raise EngineError('Invalid element level error code "%s"' % (self.err_cde)) + # PR 5: producers all emit pyx12 codes from ERROR_CODES. The + # legacy raw-X12 fallback that accepted "1"-"10" is gone; an + # unknown err_cde here is now a hard error. + if self.err_cde not in ERROR_CODES: + raise EngineError( + 'Unknown pyx12 error code "%s" (not in pyx12.error_codes.ERROR_CODES)' + % (self.err_cde) + ) diff --git a/pyx12/test/test_error_item.py b/pyx12/test/test_error_item.py index b3f545ea..741b0972 100644 --- a/pyx12/test/test_error_item.py +++ b/pyx12/test/test_error_item.py @@ -35,62 +35,78 @@ def test_invalid_code_empty(self): class TestSegError(unittest.TestCase): + # PR 5 (#TBD) tightens SegError validation to require pyx12 codes + # from pyx12.error_codes.ERROR_CODES. Raw X12 codes like "1" / "8" + # are no longer accepted. def test_valid_code(self): - e = SegError("1", "segment error") - self.assertEqual(e.err_cde, "1") + e = SegError("SEG_1_segment_not_found", "segment error") + self.assertEqual(e.err_cde, "SEG_1_segment_not_found") self.assertEqual(e.err_str, "segment error") def test_valid_code_8(self): - e = SegError("8", "seg error") - self.assertEqual(e.err_cde, "8") + e = SegError("SEG_8_trailing_terminators", "seg error") + self.assertEqual(e.err_cde, "SEG_8_trailing_terminators") def test_err_val_default_none(self): - e = SegError("1", "seg error") + e = SegError("SEG_1_segment_not_found", "seg error") self.assertIsNone(e.err_val) def test_err_val_set(self): - e = SegError("2", "seg error", err_val="BAD*SEG") + e = SegError("SEG_2_segment_not_used", "seg error", err_val="BAD*SEG") self.assertEqual(e.err_val, "BAD*SEG") def test_invalid_code_raises(self): with self.assertRaises(EngineError): SegError("99", "bad code") + def test_invalid_code_legacy_raw_X12_rejected(self): + # Raw X12 codes were accepted pre-PR-5. Now rejected. + with self.assertRaises(EngineError): + SegError("1", "bad code") + def test_invalid_code_zero(self): with self.assertRaises(EngineError): SegError("0", "bad code") class TestEleError(unittest.TestCase): + # PR 5 (#TBD) tightens EleError validation to require pyx12 codes + # from pyx12.error_codes.ERROR_CODES. Raw X12 codes like "1" / "10" + # are no longer accepted. def test_valid_code(self): - e = EleError("1", "element error") - self.assertEqual(e.err_cde, "1") + e = EleError("ELE_1_mandatory_missing", "element error") + self.assertEqual(e.err_cde, "ELE_1_mandatory_missing") self.assertEqual(e.err_str, "element error") def test_valid_code_10(self): - e = EleError("10", "element error") - self.assertEqual(e.err_cde, "10") + e = EleError("ELE_10_not_used", "element error") + self.assertEqual(e.err_cde, "ELE_10_not_used") def test_refdes_default_none(self): - e = EleError("1", "element error") + e = EleError("ELE_1_mandatory_missing", "element error") self.assertIsNone(e.refdes) def test_refdes_set(self): - e = EleError("1", "element error", refdes="03") + e = EleError("ELE_1_mandatory_missing", "element error", refdes="03") self.assertEqual(e.refdes, "03") def test_err_val_default_none(self): - e = EleError("1", "element error") + e = EleError("ELE_1_mandatory_missing", "element error") self.assertIsNone(e.err_val) def test_err_val_set(self): - e = EleError("2", "element error", err_val="BADVAL") + e = EleError("ELE_4_too_short", "element error", err_val="BADVAL") self.assertEqual(e.err_val, "BADVAL") def test_invalid_code_raises(self): with self.assertRaises(EngineError): EleError("11", "bad code") + def test_invalid_code_legacy_raw_X12_rejected(self): + # Raw X12 codes were accepted pre-PR-5. Now rejected. + with self.assertRaises(EngineError): + EleError("1", "bad code") + def test_invalid_code_zero(self): with self.assertRaises(EngineError): EleError("0", "bad code")