Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
91.07% |
51 / 56 |
|
60.00% |
3 / 5 |
CRAP | |
0.00% |
0 / 1 |
StructuredMentorListValidator | |
91.07% |
51 / 56 |
|
60.00% |
3 / 5 |
16.18 | |
0.00% |
0 / 1 |
validate | |
80.00% |
16 / 20 |
|
0.00% |
0 / 1 |
4.13 | |||
validateMentor | |
100.00% |
23 / 23 |
|
100.00% |
1 / 1 |
7 | |||
validateMentorMessage | |
100.00% |
8 / 8 |
|
100.00% |
1 / 1 |
2 | |||
validateVariable | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
getDefaultContent | |
0.00% |
0 / 1 |
|
0.00% |
0 / 1 |
2 |
1 | <?php |
2 | |
3 | namespace GrowthExperiments\Config\Validation; |
4 | |
5 | use GrowthExperiments\Mentorship\Provider\MentorProvider; |
6 | use InvalidArgumentException; |
7 | use StatusValue; |
8 | |
9 | class StructuredMentorListValidator implements IConfigValidator { |
10 | use DatatypeValidationTrait; |
11 | |
12 | /** @var string */ |
13 | private const TOP_LEVEL_KEY = 'Mentors'; |
14 | |
15 | /** |
16 | * @var string[] |
17 | * |
18 | * A mapping of keys to data types, used for validating mentor JSON object. |
19 | * |
20 | * All keys mentioned here (except keys listed in OPTIONAL_MENTOR_KEYS) will be required. |
21 | */ |
22 | private const MENTOR_KEY_DATATYPES = [ |
23 | 'username' => 'string', |
24 | 'message' => '?string', |
25 | 'weight' => 'int', |
26 | 'automaticallyAssigned' => 'bool', |
27 | ]; |
28 | |
29 | /** @var string[] List of optional keys in mentor serialization. */ |
30 | private const OPTIONAL_MENTOR_KEYS = [ |
31 | 'username', |
32 | 'automaticallyAssigned', |
33 | ]; |
34 | |
35 | /** |
36 | * @inheritDoc |
37 | */ |
38 | public function validate( array $config ): StatusValue { |
39 | if ( !array_key_exists( self::TOP_LEVEL_KEY, $config ) ) { |
40 | return StatusValue::newFatal( |
41 | 'growthexperiments-mentor-list-missing-key', |
42 | self::TOP_LEVEL_KEY |
43 | ); |
44 | } |
45 | |
46 | $mentors = $config[self::TOP_LEVEL_KEY]; |
47 | if ( !$this->validateFieldDatatype( 'array<int,array>', $mentors ) ) { |
48 | return StatusValue::newFatal( |
49 | 'growthexperiments-mentor-list-datatype-mismatch', |
50 | self::TOP_LEVEL_KEY, |
51 | 'array<int,array>', |
52 | gettype( $mentors ) |
53 | ); |
54 | } |
55 | |
56 | $status = StatusValue::newGood(); |
57 | foreach ( $mentors as $userId => $mentor ) { |
58 | $status->merge( $this->validateMentor( |
59 | $mentor, |
60 | $userId |
61 | ) ); |
62 | } |
63 | |
64 | return $status; |
65 | } |
66 | |
67 | /** |
68 | * Validate a mentor JSON representation |
69 | * |
70 | * @param array $mentor |
71 | * @param int $userId User ID of the user represented by $mentor |
72 | * @return StatusValue |
73 | */ |
74 | private function validateMentor( array $mentor, int $userId ): StatusValue { |
75 | $supportedKeys = array_keys( self::MENTOR_KEY_DATATYPES ); |
76 | |
77 | // Ensure all supported keys are present in the mentor object |
78 | foreach ( $supportedKeys as $key ) { |
79 | if ( !array_key_exists( $key, $mentor ) && !in_array( $key, self::OPTIONAL_MENTOR_KEYS ) ) { |
80 | return StatusValue::newFatal( |
81 | 'growthexperiments-mentor-list-missing-key', |
82 | $key |
83 | ); |
84 | } |
85 | } |
86 | |
87 | // Ensure all keys present in the mentor object are supported and of correct data type |
88 | foreach ( $mentor as $key => $value ) { |
89 | if ( !array_key_exists( $key, self::MENTOR_KEY_DATATYPES ) ) { |
90 | return StatusValue::newFatal( |
91 | 'growthexperiments-mentor-list-unexpected-key-mentor', |
92 | $key |
93 | ); |
94 | } |
95 | |
96 | if ( !$this->validateFieldDatatype( self::MENTOR_KEY_DATATYPES[$key], $value ) ) { |
97 | return StatusValue::newFatal( |
98 | 'growthexperiments-mentor-list-datatype-mismatch', |
99 | $key, |
100 | self::MENTOR_KEY_DATATYPES[$key], |
101 | gettype( $value ) |
102 | ); |
103 | } |
104 | } |
105 | |
106 | // Code below assumes mentor declarations are syntactically correct. |
107 | $status = StatusValue::newGood(); |
108 | $status->merge( self::validateMentorMessage( $mentor, $userId ) ); |
109 | return $status; |
110 | } |
111 | |
112 | /** |
113 | * Validate the mentor message |
114 | * |
115 | * Currently only checks MentorProvider::INTRO_TEXT_LENGTH. |
116 | * |
117 | * @param array $mentor |
118 | * @param int $userId User ID of the user represented by $mentor |
119 | * @return StatusValue Warning means "an issue, but not important enough to stop using the |
120 | * mentor list". |
121 | */ |
122 | public static function validateMentorMessage( |
123 | array $mentor, |
124 | int $userId |
125 | ): StatusValue { |
126 | $status = StatusValue::newGood(); |
127 | |
128 | // Ensure message has correct length. This only warns, as we do not want to fail the |
129 | // validation (truncated message is better than broken mentorship). |
130 | if ( mb_strlen( $mentor['message'] ?? '', 'UTF-8' ) > MentorProvider::INTRO_TEXT_LENGTH ) { |
131 | $status->warning( |
132 | 'growthexperiments-mentor-writer-error-message-too-long', |
133 | MentorProvider::INTRO_TEXT_LENGTH, |
134 | $userId |
135 | ); |
136 | } |
137 | |
138 | return $status; |
139 | } |
140 | |
141 | /** |
142 | * @inheritDoc |
143 | */ |
144 | public function validateVariable( string $variable, $value ): void { |
145 | if ( $variable !== self::TOP_LEVEL_KEY ) { |
146 | throw new InvalidArgumentException( |
147 | "Invalid variable $variable configured in the mentor list" |
148 | ); |
149 | } |
150 | } |
151 | |
152 | /** |
153 | * @inheritDoc |
154 | */ |
155 | public function getDefaultContent(): array { |
156 | return [ 'Mentors' => [] ]; |
157 | } |
158 | } |