Coverage for /home/admin/Documents/AI/applications/lexigram-dev/lexigram/experimental/ai/lexigram-ai-relay-gateway/src/lexigram/ai/relay/gateway/validation.py: 18%

33 statements  

« prev     ^ index     » next       coverage.py v7.15.4, created at 2026-08-25 07:19 +0800

1"""Request validation for the relay gateway service. 

2 

3``validate_gateway_request`` is the boundary check run by 

4``RelayGatewayService.handle`` before any dependency runs; malformed 

5requests short-circuit with ``Err(RelayGatewayError)`` (400) instead of 

6allowing malformed data to fail downstream. 

7""" 

8 

9from __future__ import annotations 

10 

11from collections.abc import Mapping 

12 

13from lexigram.contracts.ai.relay import RelayGatewayError, RelayGatewayRequest 

14from lexigram.contracts.ai.relay.gateway import RelayGatewayErrorCode 

15 

16__all__ = ["validate_gateway_request"] 

17 

18 

19def validate_gateway_request( 

20 request: RelayGatewayRequest, 

21) -> RelayGatewayError | None: 

22 """Reject malformed gateway requests before the pipeline runs. 

23 

24 Args: 

25 request: The gateway request to validate. 

26 

27 Returns: 

28 ``None`` when the request is well-formed, otherwise the 

29 ``INVALID_REQUEST`` gateway error describing the defect. 

30 """ 

31 if not isinstance(request.request_id, str) or not request.request_id: 

32 message = "request_id is required" 

33 return RelayGatewayError( 

34 code=RelayGatewayErrorCode.INVALID_REQUEST, 

35 message=message, 

36 status_code=400, 

37 request_id=getattr(request, "request_id", None) or "", 

38 ) 

39 if not isinstance(request.tenant_id, str) or not request.tenant_id: 

40 message = "tenant_id is required" 

41 return RelayGatewayError( 

42 code=RelayGatewayErrorCode.INVALID_REQUEST, 

43 message=message, 

44 status_code=400, 

45 request_id=request.request_id, 

46 ) 

47 if not isinstance(request.model, str) or not request.model: 

48 message = "model is required" 

49 return RelayGatewayError( 

50 code=RelayGatewayErrorCode.INVALID_REQUEST, 

51 message=message, 

52 status_code=400, 

53 request_id=request.request_id, 

54 ) 

55 if not isinstance(request.source, str) or not request.source: 

56 message = "source is required" 

57 return RelayGatewayError( 

58 code=RelayGatewayErrorCode.INVALID_REQUEST, 

59 message=message, 

60 status_code=400, 

61 request_id=request.request_id, 

62 ) 

63 if not isinstance(request.payload, Mapping) or not request.payload: 

64 message = "payload must be a non-empty object" 

65 return RelayGatewayError( 

66 code=RelayGatewayErrorCode.INVALID_REQUEST, 

67 message=message, 

68 status_code=400, 

69 request_id=request.request_id, 

70 ) 

71 messages = request.payload.get("messages") 

72 if messages is not None: 

73 if not isinstance(messages, list): 

74 message = "payload messages must be a list" 

75 return RelayGatewayError( 

76 code=RelayGatewayErrorCode.INVALID_REQUEST, 

77 message=message, 

78 status_code=400, 

79 request_id=request.request_id, 

80 ) 

81 if any(not isinstance(entry, Mapping) for entry in messages): 

82 message = "payload messages entries must be objects" 

83 return RelayGatewayError( 

84 code=RelayGatewayErrorCode.INVALID_REQUEST, 

85 message=message, 

86 status_code=400, 

87 request_id=request.request_id, 

88 ) 

89 if not isinstance(request.headers, Mapping): 

90 message = "headers must be an object" 

91 return RelayGatewayError( 

92 code=RelayGatewayErrorCode.INVALID_REQUEST, 

93 message=message, 

94 status_code=400, 

95 request_id=request.request_id, 

96 ) 

97 return None