@@ -522,100 +522,22 @@ pub(crate) enum RulesetEnforcement {
522522#[ derive( Clone , Debug , PartialEq , Eq , serde:: Serialize , serde:: Deserialize ) ]
523523pub ( crate ) struct RulesetBypassActor {
524524 /// The ID of the actor that can bypass a ruleset.
525- /// - Required for Integration, RepositoryRole, and Team actor types (use Some(ActorId::Id(n)))
526- /// - Must be 1 for OrganizationAdmin (use Some(ActorId::Id(1)))
527- /// - Must be null for DeployKey (use Some(ActorId::Null))
528- /// - Omitted for EnterpriseOwner (use None)
529- #[ serde(
530- skip_serializing_if = "Option::is_none" ,
531- default ,
532- deserialize_with = "deserialize_actor_id"
533- ) ]
534- pub ( crate ) actor_id : Option < ActorId > ,
525+ /// Required for Team and Integration actor types.
526+ #[ serde( skip_serializing_if = "Option::is_none" ) ]
527+ pub ( crate ) actor_id : Option < i64 > ,
535528 pub ( crate ) actor_type : RulesetActorType ,
536529 /// The bypass mode for the actor. Defaults to "always" per GitHub API.
537- /// - always: Actor can always bypass
538- /// - pull_request: Actor can only bypass on pull requests (not applicable for DeployKey)
539- /// - exempt: Rules won't run for actor, no audit entry created
540530 #[ serde( skip_serializing_if = "Option::is_none" ) ]
541531 pub ( crate ) bypass_mode : Option < RulesetBypassMode > ,
542532}
543533
544- /// Custom deserializer for actor_id that distinguishes between null and missing
545- fn deserialize_actor_id < ' de , D > ( deserializer : D ) -> Result < Option < ActorId > , D :: Error >
546- where
547- D : serde:: Deserializer < ' de > ,
548- {
549- use serde:: { Deserialize , de} ;
550-
551- struct ActorIdVisitor ;
552-
553- impl < ' de > de:: Visitor < ' de > for ActorIdVisitor {
554- type Value = Option < ActorId > ;
555-
556- fn expecting ( & self , formatter : & mut std:: fmt:: Formatter ) -> std:: fmt:: Result {
557- formatter. write_str ( "an integer, null, or missing field" )
558- }
559-
560- fn visit_none < E > ( self ) -> Result < Self :: Value , E >
561- where
562- E : de:: Error ,
563- {
564- Ok ( Some ( ActorId :: Null ) )
565- }
566-
567- fn visit_some < D > ( self , deserializer : D ) -> Result < Self :: Value , D :: Error >
568- where
569- D : serde:: Deserializer < ' de > ,
570- {
571- let id = i64:: deserialize ( deserializer) ?;
572- Ok ( Some ( ActorId :: Id ( id) ) )
573- }
574-
575- fn visit_unit < E > ( self ) -> Result < Self :: Value , E >
576- where
577- E : de:: Error ,
578- {
579- Ok ( Some ( ActorId :: Null ) )
580- }
581-
582- fn visit_i64 < E > ( self , v : i64 ) -> Result < Self :: Value , E >
583- where
584- E : de:: Error ,
585- {
586- Ok ( Some ( ActorId :: Id ( v) ) )
587- }
588-
589- fn visit_u64 < E > ( self , v : u64 ) -> Result < Self :: Value , E >
590- where
591- E : de:: Error ,
592- {
593- Ok ( Some ( ActorId :: Id ( v as i64 ) ) )
594- }
595- }
596-
597- deserializer. deserialize_option ( ActorIdVisitor )
598- }
599-
600- /// Represents the actor_id field which can be a number or null
601- #[ derive( Clone , Debug , PartialEq , Eq , serde:: Serialize , serde:: Deserialize ) ]
602- #[ serde( untagged) ]
603- pub ( crate ) enum ActorId {
604- /// A specific actor ID (used for Integration, RepositoryRole, Team, OrganizationAdmin)
605- Id ( i64 ) ,
606- /// Explicitly null (required for DeployKey)
607- Null ,
608- }
609-
610534#[ derive( Clone , Debug , PartialEq , Eq , serde:: Serialize , serde:: Deserialize ) ]
611535#[ serde( rename_all = "PascalCase" ) ]
612536pub ( crate ) enum RulesetActorType {
537+ /// GitHub App integration
613538 Integration ,
614- OrganizationAdmin ,
615- RepositoryRole ,
539+ /// GitHub Team
616540 Team ,
617- DeployKey ,
618- EnterpriseOwner ,
619541}
620542
621543#[ derive( Clone , Debug , PartialEq , Eq , serde:: Serialize , serde:: Deserialize ) ]
@@ -730,7 +652,7 @@ mod tests {
730652 fn test_bypass_actor_serialization ( ) {
731653 // Test Team actor with ID
732654 let team_actor = RulesetBypassActor {
733- actor_id : Some ( ActorId :: Id ( 234 ) ) ,
655+ actor_id : Some ( 234 ) ,
734656 actor_type : RulesetActorType :: Team ,
735657 bypass_mode : Some ( RulesetBypassMode :: Always ) ,
736658 } ;
@@ -743,7 +665,7 @@ mod tests {
743665
744666 // Test Integration actor with ID
745667 let integration_actor = RulesetBypassActor {
746- actor_id : Some ( ActorId :: Id ( 123456 ) ) ,
668+ actor_id : Some ( 123456 ) ,
747669 actor_type : RulesetActorType :: Integration ,
748670 bypass_mode : Some ( RulesetBypassMode :: Always ) ,
749671 } ;
@@ -754,48 +676,22 @@ mod tests {
754676 "Integration actor should serialize with numeric actor_id"
755677 ) ;
756678
757- // Test OrganizationAdmin with ID 1 (per GitHub API spec)
758- let org_admin_actor = RulesetBypassActor {
759- actor_id : Some ( ActorId :: Id ( 1 ) ) ,
760- actor_type : RulesetActorType :: OrganizationAdmin ,
761- bypass_mode : None , // Use API default
762- } ;
763- let json = serde_json:: to_string ( & org_admin_actor)
764- . expect ( "OrganizationAdmin actor serialization should succeed" ) ;
765- assert_eq ! (
766- json, r#"{"actor_id":1,"actor_type":"OrganizationAdmin"}"# ,
767- "OrganizationAdmin should use actor_id:1 and omit bypass_mode when None"
768- ) ;
769-
770- // Test DeployKey with null actor_id (per GitHub API spec)
771- let deploy_key_actor = RulesetBypassActor {
772- actor_id : Some ( ActorId :: Null ) ,
773- actor_type : RulesetActorType :: DeployKey ,
774- bypass_mode : Some ( RulesetBypassMode :: Always ) ,
775- } ;
776- let json = serde_json:: to_string ( & deploy_key_actor)
777- . expect ( "DeployKey actor serialization should succeed" ) ;
778- assert_eq ! (
779- json, r#"{"actor_id":null,"actor_type":"DeployKey","bypass_mode":"always"}"# ,
780- "DeployKey should serialize actor_id as null"
781- ) ;
782-
783- // Test EnterpriseOwner with None actor_id (field omitted per GitHub API spec)
784- let enterprise_actor = RulesetBypassActor {
679+ // Test with None actor_id (field omitted)
680+ let actor_no_id = RulesetBypassActor {
785681 actor_id : None ,
786- actor_type : RulesetActorType :: EnterpriseOwner ,
787- bypass_mode : Some ( RulesetBypassMode :: Exempt ) ,
682+ actor_type : RulesetActorType :: Team ,
683+ bypass_mode : Some ( RulesetBypassMode :: Always ) ,
788684 } ;
789- let json = serde_json:: to_string ( & enterprise_actor )
790- . expect ( "EnterpriseOwner actor serialization should succeed" ) ;
685+ let json = serde_json:: to_string ( & actor_no_id )
686+ . expect ( "Actor without ID serialization should succeed" ) ;
791687 assert_eq ! (
792- json, r#"{"actor_type":"EnterpriseOwner ","bypass_mode":"exempt "}"# ,
793- "EnterpriseOwner should omit actor_id field entirely "
688+ json, r#"{"actor_type":"Team ","bypass_mode":"always "}"# ,
689+ "Actor without ID should omit actor_id field"
794690 ) ;
795691
796692 // Test pull_request bypass mode
797693 let pr_actor = RulesetBypassActor {
798- actor_id : Some ( ActorId :: Id ( 789 ) ) ,
694+ actor_id : Some ( 789 ) ,
799695 actor_type : RulesetActorType :: Team ,
800696 bypass_mode : Some ( RulesetBypassMode :: PullRequest ) ,
801697 } ;
@@ -809,15 +705,11 @@ mod tests {
809705
810706 #[ test]
811707 fn test_bypass_actor_deserialization ( ) {
812- // Test deserializing from GitHub API response
708+ // Test deserializing Team actor from GitHub API response
813709 let json = r#"{"actor_id":234,"actor_type":"Team","bypass_mode":"always"}"# ;
814710 let actor: RulesetBypassActor =
815711 serde_json:: from_str ( json) . expect ( "Should deserialize valid Team actor" ) ;
816- assert_eq ! (
817- actor. actor_id,
818- Some ( ActorId :: Id ( 234 ) ) ,
819- "actor_id should be numeric"
820- ) ;
712+ assert_eq ! ( actor. actor_id, Some ( 234 ) , "actor_id should be numeric" ) ;
821713 assert_eq ! (
822714 actor. actor_type,
823715 RulesetActorType :: Team ,
@@ -829,62 +721,23 @@ mod tests {
829721 "bypass_mode should be Always"
830722 ) ;
831723
832- // Test with null actor_id (DeployKey)
833- // Custom deserializer ensures JSON null becomes Some(ActorId::Null), not None
834- let json = r#"{"actor_id":null,"actor_type":"DeployKey","bypass_mode":"always"}"# ;
835- let actor: RulesetBypassActor = serde_json:: from_str ( json)
836- . expect ( "Should deserialize DeployKey actor with null actor_id" ) ;
837- assert_eq ! (
838- actor. actor_id,
839- Some ( ActorId :: Null ) ,
840- "JSON null should deserialize to Some(ActorId::Null) with custom deserializer"
841- ) ;
842- assert_eq ! ( actor. actor_type, RulesetActorType :: DeployKey ) ;
724+ // Test deserializing Integration actor
725+ let json = r#"{"actor_id":456,"actor_type":"Integration","bypass_mode":"always"}"# ;
726+ let actor: RulesetBypassActor =
727+ serde_json:: from_str ( json) . expect ( "Should deserialize valid Integration actor" ) ;
728+ assert_eq ! ( actor. actor_id, Some ( 456 ) ) ;
729+ assert_eq ! ( actor. actor_type, RulesetActorType :: Integration ) ;
843730
844731 // Test with missing bypass_mode (should default to None)
845- let json = r#"{"actor_id":1,"actor_type":"OrganizationAdmin "}"# ;
846- let actor: RulesetBypassActor = serde_json :: from_str ( json )
847- . expect ( "Should deserialize OrganizationAdmin without bypass_mode" ) ;
848- assert_eq ! ( actor. actor_id, Some ( ActorId :: Id ( 1 ) ) ) ;
732+ let json = r#"{"actor_id":1,"actor_type":"Team "}"# ;
733+ let actor: RulesetBypassActor =
734+ serde_json :: from_str ( json ) . expect ( "Should deserialize Team without bypass_mode" ) ;
735+ assert_eq ! ( actor. actor_id, Some ( 1 ) ) ;
849736 assert_eq ! (
850737 actor. bypass_mode, None ,
851738 "bypass_mode should be None when omitted from JSON"
852739 ) ;
853740
854- // Test with missing actor_id (EnterpriseOwner)
855- let json = r#"{"actor_type":"EnterpriseOwner","bypass_mode":"exempt"}"# ;
856- let actor: RulesetBypassActor = serde_json:: from_str ( json)
857- . expect ( "Should deserialize EnterpriseOwner without actor_id" ) ;
858- assert_eq ! (
859- actor. actor_id, None ,
860- "actor_id should be None when omitted from JSON"
861- ) ;
862- assert_eq ! ( actor. actor_type, RulesetActorType :: EnterpriseOwner ) ;
863- assert_eq ! ( actor. bypass_mode, Some ( RulesetBypassMode :: Exempt ) ) ;
864-
865- // Test all actor types can be deserialized
866- let actor_types = [
867- ( "Integration" , RulesetActorType :: Integration ) ,
868- ( "OrganizationAdmin" , RulesetActorType :: OrganizationAdmin ) ,
869- ( "RepositoryRole" , RulesetActorType :: RepositoryRole ) ,
870- ( "Team" , RulesetActorType :: Team ) ,
871- ( "DeployKey" , RulesetActorType :: DeployKey ) ,
872- ( "EnterpriseOwner" , RulesetActorType :: EnterpriseOwner ) ,
873- ] ;
874- for ( type_str, expected_type) in actor_types {
875- let json = format ! (
876- r#"{{"actor_id":1,"actor_type":"{}","bypass_mode":"always"}}"# ,
877- type_str
878- ) ;
879- let actor: RulesetBypassActor = serde_json:: from_str ( & json)
880- . unwrap_or_else ( |e| panic ! ( "Should deserialize actor type {}: {}" , type_str, e) ) ;
881- assert_eq ! (
882- actor. actor_type, expected_type,
883- "actor_type {} should deserialize correctly" ,
884- type_str
885- ) ;
886- }
887-
888741 // Test all bypass modes can be deserialized
889742 let bypass_modes = [
890743 ( "always" , RulesetBypassMode :: Always ) ,
0 commit comments