@@ -85,6 +85,27 @@ fn convert_proto_metadata(metadata: prost_types::Struct) -> FlagMetadata {
8585 FlagMetadata { values }
8686}
8787
88+ /// Maps gRPC status codes to OpenFeature error codes
89+ ///
90+ /// This ensures consistent error handling across different resolver types
91+ /// and proper conformance with the OpenFeature specification.
92+ fn map_grpc_status_to_error_code ( status : & tonic:: Status ) -> EvaluationErrorCode {
93+ use tonic:: Code ;
94+ match status. code ( ) {
95+ Code :: NotFound => EvaluationErrorCode :: FlagNotFound ,
96+ Code :: InvalidArgument => EvaluationErrorCode :: InvalidContext ,
97+ Code :: Unauthenticated | Code :: PermissionDenied => {
98+ EvaluationErrorCode :: General ( "authentication/authorization error" . to_string ( ) )
99+ }
100+ Code :: FailedPrecondition => EvaluationErrorCode :: TypeMismatch ,
101+ Code :: DeadlineExceeded | Code :: Cancelled => {
102+ EvaluationErrorCode :: General ( "request timeout or cancelled" . to_string ( ) )
103+ }
104+ Code :: Unavailable => EvaluationErrorCode :: General ( "service unavailable" . to_string ( ) ) ,
105+ _ => EvaluationErrorCode :: General ( format ! ( "{:?}" , status. code( ) ) ) ,
106+ }
107+ }
108+
88109pub struct RpcResolver {
89110 client : ClientType ,
90111 metadata : OnceLock < ProviderMetadata > ,
@@ -214,7 +235,7 @@ impl FeatureProvider for RpcResolver {
214235 Err ( status) => {
215236 error ! ( flag_key, error = %status, "failed to resolve boolean flag" ) ;
216237 Err ( EvaluationError {
217- code : EvaluationErrorCode :: General ( status. code ( ) . to_string ( ) ) ,
238+ code : map_grpc_status_to_error_code ( & status) ,
218239 message : Some ( status. message ( ) . to_string ( ) ) ,
219240 } )
220241 }
@@ -247,7 +268,7 @@ impl FeatureProvider for RpcResolver {
247268 Err ( status) => {
248269 error ! ( flag_key, error = %status, "failed to resolve string flag" ) ;
249270 Err ( EvaluationError {
250- code : EvaluationErrorCode :: General ( status. code ( ) . to_string ( ) ) ,
271+ code : map_grpc_status_to_error_code ( & status) ,
251272 message : Some ( status. message ( ) . to_string ( ) ) ,
252273 } )
253274 }
@@ -280,7 +301,7 @@ impl FeatureProvider for RpcResolver {
280301 Err ( status) => {
281302 error ! ( flag_key, error = %status, "failed to resolve float flag" ) ;
282303 Err ( EvaluationError {
283- code : EvaluationErrorCode :: General ( status. code ( ) . to_string ( ) ) ,
304+ code : map_grpc_status_to_error_code ( & status) ,
284305 message : Some ( status. message ( ) . to_string ( ) ) ,
285306 } )
286307 }
@@ -313,7 +334,7 @@ impl FeatureProvider for RpcResolver {
313334 Err ( status) => {
314335 error ! ( flag_key, error = %status, "failed to resolve integer flag" ) ;
315336 Err ( EvaluationError {
316- code : EvaluationErrorCode :: General ( status. code ( ) . to_string ( ) ) ,
337+ code : map_grpc_status_to_error_code ( & status) ,
317338 message : Some ( status. message ( ) . to_string ( ) ) ,
318339 } )
319340 }
@@ -346,7 +367,7 @@ impl FeatureProvider for RpcResolver {
346367 Err ( status) => {
347368 error ! ( flag_key, error = %status, "failed to resolve struct flag" ) ;
348369 Err ( EvaluationError {
349- code : EvaluationErrorCode :: General ( status. code ( ) . to_string ( ) ) ,
370+ code : map_grpc_status_to_error_code ( & status) ,
350371 message : Some ( status. message ( ) . to_string ( ) ) ,
351372 } )
352373 }
@@ -765,4 +786,44 @@ mod tests {
765786 // Clean shutdown
766787 server_handle. abort ( ) ;
767788 }
789+
790+ #[ test]
791+ fn test_grpc_error_code_mapping ( ) {
792+ use tonic:: Code ;
793+
794+ // Test NOT_FOUND -> FlagNotFound
795+ let status = tonic:: Status :: new ( Code :: NotFound , "Flag not found" ) ;
796+ let error_code = map_grpc_status_to_error_code ( & status) ;
797+ assert ! ( matches!( error_code, EvaluationErrorCode :: FlagNotFound ) ) ;
798+
799+ // Test INVALID_ARGUMENT -> InvalidContext
800+ let status = tonic:: Status :: new ( Code :: InvalidArgument , "Invalid context" ) ;
801+ let error_code = map_grpc_status_to_error_code ( & status) ;
802+ assert ! ( matches!( error_code, EvaluationErrorCode :: InvalidContext ) ) ;
803+
804+ // Test UNAUTHENTICATED -> General
805+ let status = tonic:: Status :: new ( Code :: Unauthenticated , "Not authenticated" ) ;
806+ let error_code = map_grpc_status_to_error_code ( & status) ;
807+ assert ! ( matches!( error_code, EvaluationErrorCode :: General ( _) ) ) ;
808+
809+ // Test PERMISSION_DENIED -> General
810+ let status = tonic:: Status :: new ( Code :: PermissionDenied , "Access denied" ) ;
811+ let error_code = map_grpc_status_to_error_code ( & status) ;
812+ assert ! ( matches!( error_code, EvaluationErrorCode :: General ( _) ) ) ;
813+
814+ // Test FAILED_PRECONDITION -> TypeMismatch
815+ let status = tonic:: Status :: new ( Code :: FailedPrecondition , "Type mismatch" ) ;
816+ let error_code = map_grpc_status_to_error_code ( & status) ;
817+ assert ! ( matches!( error_code, EvaluationErrorCode :: TypeMismatch ) ) ;
818+
819+ // Test DEADLINE_EXCEEDED -> General
820+ let status = tonic:: Status :: new ( Code :: DeadlineExceeded , "Timeout" ) ;
821+ let error_code = map_grpc_status_to_error_code ( & status) ;
822+ assert ! ( matches!( error_code, EvaluationErrorCode :: General ( _) ) ) ;
823+
824+ // Test UNAVAILABLE -> General
825+ let status = tonic:: Status :: new ( Code :: Unavailable , "Service unavailable" ) ;
826+ let error_code = map_grpc_status_to_error_code ( & status) ;
827+ assert ! ( matches!( error_code, EvaluationErrorCode :: General ( _) ) ) ;
828+ }
768829}
0 commit comments