@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Add basic typechecking support to Conduit

Summary:
Ref T9964. I want to show users what we're expecting in "constraints", and let constraints like "authors=epriestley" work to make things easier.

I'm generally very happy with the "HTTPParameterType" stuff from EditEngine, so add a parallel set of "ConduitParameterType" classes. These are a little simpler than the HTTP ones, but have a little more validation logic.

Test Plan:
This is really just a proof of concept; some of these fields are now filled in:

{F1023845}

Reviewers: chad

Reviewed By: chad

Maniphest Tasks: T9964

Differential Revision: https://secure.phabricator.com/D14763

+346 -27
+8
src/__phutil_library_map__.php
··· 231 231 'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php', 232 232 'ConduitGetCapabilitiesConduitAPIMethod' => 'applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php', 233 233 'ConduitGetCertificateConduitAPIMethod' => 'applications/conduit/method/ConduitGetCertificateConduitAPIMethod.php', 234 + 'ConduitListParameterType' => 'applications/conduit/parametertype/ConduitListParameterType.php', 234 235 'ConduitLogGarbageCollector' => 'applications/conduit/garbagecollector/ConduitLogGarbageCollector.php', 235 236 'ConduitMethodDoesNotExistException' => 'applications/conduit/protocol/exception/ConduitMethodDoesNotExistException.php', 236 237 'ConduitMethodNotFoundException' => 'applications/conduit/protocol/exception/ConduitMethodNotFoundException.php', 238 + 'ConduitParameterType' => 'applications/conduit/parametertype/ConduitParameterType.php', 237 239 'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php', 238 240 'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php', 239 241 'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php', 240 242 'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php', 243 + 'ConduitStringListParameterType' => 'applications/conduit/parametertype/ConduitStringListParameterType.php', 241 244 'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php', 245 + 'ConduitUserListParameterType' => 'applications/conduit/parametertype/ConduitUserListParameterType.php', 242 246 'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php', 243 247 'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php', 244 248 'ConpherenceConfigOptions' => 'applications/conpherence/config/ConpherenceConfigOptions.php', ··· 4071 4075 'ConduitException' => 'Exception', 4072 4076 'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod', 4073 4077 'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod', 4078 + 'ConduitListParameterType' => 'ConduitParameterType', 4074 4079 'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector', 4075 4080 'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException', 4076 4081 'ConduitMethodNotFoundException' => 'ConduitException', 4082 + 'ConduitParameterType' => 'Phobject', 4077 4083 'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod', 4078 4084 'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod', 4079 4085 'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 4080 4086 'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow', 4087 + 'ConduitStringListParameterType' => 'ConduitListParameterType', 4081 4088 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', 4089 + 'ConduitUserListParameterType' => 'ConduitListParameterType', 4082 4090 'ConpherenceColumnViewController' => 'ConpherenceController', 4083 4091 'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod', 4084 4092 'ConpherenceConfigOptions' => 'PhabricatorApplicationConfigOptions',
+37
src/applications/conduit/parametertype/ConduitListParameterType.php
··· 1 + <?php 2 + 3 + abstract class ConduitListParameterType 4 + extends ConduitParameterType { 5 + 6 + protected function getParameterValue(array $request, $key) { 7 + $value = parent::getParameterValue(); 8 + 9 + if (!is_array($value)) { 10 + $this->raiseValidationException( 11 + $request, 12 + $key, 13 + pht('Expected a list, but value is not a list.')); 14 + } 15 + 16 + $actual_keys = array_keys($value); 17 + if ($value) { 18 + $natural_keys = range(0, count($value) - 1); 19 + } else { 20 + $natural_keys = array(); 21 + } 22 + 23 + if ($actual_keys !== $natural_keys) { 24 + $this->raiseValidationException( 25 + $request, 26 + $key, 27 + pht('Expected a list, but value is an object.')); 28 + } 29 + 30 + return $value; 31 + } 32 + 33 + protected function getParameterDefault() { 34 + return array(); 35 + } 36 + 37 + }
+97
src/applications/conduit/parametertype/ConduitParameterType.php
··· 1 + <?php 2 + 3 + /** 4 + * Defines how to read a value from a Conduit request. 5 + * 6 + * This class behaves like @{class:AphrontHTTPParameterType}, but for Conduit. 7 + */ 8 + abstract class ConduitParameterType extends Phobject { 9 + 10 + 11 + private $viewer; 12 + 13 + 14 + final public function setViewer(PhabricatorUser $viewer) { 15 + $this->viewer = $viewer; 16 + return $this; 17 + } 18 + 19 + 20 + final public function getViewer() { 21 + if (!$this->viewer) { 22 + throw new PhutilInvalidStateException('setViewer'); 23 + } 24 + return $this->viewer; 25 + } 26 + 27 + 28 + final public function getExists(array $request, $key) { 29 + return $this->getValueExists($request, $key); 30 + } 31 + 32 + 33 + final public function getValue(array $request, $key) { 34 + if (!$this->getExists($request, $key)) { 35 + return $this->getParameterDefault(); 36 + } 37 + 38 + return $this->getParameterValue($request, $key); 39 + } 40 + 41 + 42 + final public function getDefaultValue() { 43 + return $this->getParameterDefault(); 44 + } 45 + 46 + 47 + final public function getTypeName() { 48 + return $this->getParameterTypeName(); 49 + } 50 + 51 + 52 + final public function getFormatDescriptions() { 53 + return $this->getParameterFormatDescriptions(); 54 + } 55 + 56 + 57 + final public function getExamples() { 58 + return $this->getParameterExamples(); 59 + } 60 + 61 + protected function raiseValidationException(array $request, $key, $message) { 62 + // TODO: Specialize this so we can give users more tailored messages from 63 + // Conduit. 64 + throw new Exception($message); 65 + } 66 + 67 + 68 + final public static function getAllTypes() { 69 + return id(new PhutilClassMapQuery()) 70 + ->setAncestorClass(__CLASS__) 71 + ->setUniqueMethod('getTypeName') 72 + ->setSortMethod('getTypeName') 73 + ->execute(); 74 + } 75 + 76 + 77 + protected function getParameterExists(array $request, $key) { 78 + return array_key_exists($key, $request); 79 + } 80 + 81 + protected function getParameterValue(array $request, $key) { 82 + return $request[$key]; 83 + } 84 + 85 + abstract protected function getParameterTypeName(); 86 + 87 + 88 + abstract protected function getParameterFormatDescriptions(); 89 + 90 + 91 + abstract protected function getParameterExamples(); 92 + 93 + protected function getParameterDefault() { 94 + return null; 95 + } 96 + 97 + }
+40
src/applications/conduit/parametertype/ConduitStringListParameterType.php
··· 1 + <?php 2 + 3 + final class ConduitStringListParameterType 4 + extends ConduitListParameterType { 5 + 6 + protected function getParameterValue(array $request, $key) { 7 + $list = parent::getParameterValue(); 8 + 9 + foreach ($list as $idx => $item) { 10 + if (!is_string($item)) { 11 + $this->raiseValidationException( 12 + $request, 13 + $key, 14 + pht( 15 + 'Expected a list of strings, but item with index "%s" is '. 16 + 'not a string.', 17 + $idx)); 18 + } 19 + } 20 + 21 + return $list; 22 + } 23 + 24 + protected function getParameterTypeName() { 25 + return 'list<string>'; 26 + } 27 + 28 + protected function getParameterFormatDescriptions() { 29 + return array( 30 + pht('List of strings.'), 31 + ); 32 + } 33 + 34 + protected function getParameterExamples() { 35 + return array( 36 + '["mango", "nectarine"]', 37 + ); 38 + } 39 + 40 + }
+33
src/applications/conduit/parametertype/ConduitUserListParameterType.php
··· 1 + <?php 2 + 3 + final class ConduitUserListParameterType 4 + extends ConduitListParameterType { 5 + 6 + protected function getParameterValue(array $request, $key) { 7 + $list = parent::getParameterValue(); 8 + return id(new PhabricatorUserPHIDResolver()) 9 + ->setViewer($this->getViewer()) 10 + ->resolvePHIDs($list); 11 + } 12 + 13 + protected function getParameterTypeName() { 14 + return 'list<user>'; 15 + } 16 + 17 + protected function getParameterFormatDescriptions() { 18 + return array( 19 + pht('List of user PHIDs.'), 20 + pht('List of usernames.'), 21 + pht('List with a mixture of PHIDs and usernames.'), 22 + ); 23 + } 24 + 25 + protected function getParameterExamples() { 26 + return array( 27 + '["PHID-USER-1111"]', 28 + '["alincoln"]', 29 + '["PHID-USER-2222", "alincoln"]', 30 + ); 31 + } 32 + 33 + }
+4
src/applications/conduit/protocol/ConduitAPIRequest.php
··· 14 14 return coalesce(idx($this->params, $key), $default); 15 15 } 16 16 17 + public function getValueExists($key) { 18 + return array_key_exists($key, $this->params); 19 + } 20 + 17 21 public function getAllParameters() { 18 22 return $this->params; 19 23 }
+1
src/applications/paste/query/PhabricatorPasteSearchEngine.php
··· 47 47 id(new PhabricatorUsersSearchField()) 48 48 ->setAliases(array('authors')) 49 49 ->setKey('authorPHIDs') 50 + ->setConduitKey('authors') 50 51 ->setLabel(pht('Authors')), 51 52 id(new PhabricatorSearchStringListField()) 52 53 ->setKey('languages')
+4
src/applications/people/searchfield/PhabricatorUsersSearchField.php
··· 15 15 return new PhabricatorPeopleUserFunctionDatasource(); 16 16 } 17 17 18 + protected function newConduitParameterType() { 19 + return new ConduitUserListParameterType(); 20 + } 21 + 18 22 }
+36 -6
src/applications/search/engine/PhabricatorApplicationSearchEngine.php
··· 1173 1173 } 1174 1174 1175 1175 public function getSearchFieldsForConduit() { 1176 - $fields = $this->buildSearchFields(); 1176 + $standard_fields = $this->buildSearchFields(); 1177 + 1178 + $fields = array(); 1179 + foreach ($standard_fields as $field_key => $field) { 1180 + $conduit_key = $field->getConduitKey(); 1181 + 1182 + if (isset($fields[$conduit_key])) { 1183 + $other = $fields[$conduit_key]; 1184 + $other_key = $other->getKey(); 1185 + 1186 + throw new Exception( 1187 + pht( 1188 + 'SearchFields "%s" (of class "%s") and "%s" (of class "%s") both '. 1189 + 'define the same Conduit key ("%s"). Keys must be unique.', 1190 + $field_key, 1191 + get_class($field), 1192 + $other_key, 1193 + get_class($other), 1194 + $conduit_key)); 1195 + } 1196 + 1197 + $fields[$conduit_key] = $field; 1198 + } 1199 + 1200 + $viewer = $this->requireViewer(); 1201 + foreach ($fields as $key => $field) { 1202 + $field->setViewer($viewer); 1203 + } 1177 1204 1178 1205 // These are handled separately for Conduit, so don't show them as 1179 1206 // supported. ··· 1187 1214 1188 1215 public function buildConduitResponse(ConduitAPIRequest $request) { 1189 1216 $viewer = $this->requireViewer(); 1190 - $fields = $this->buildSearchFields(); 1191 1217 1192 1218 $query_key = $request->getValue('queryKey'); 1193 1219 if (!strlen($query_key)) { ··· 1207 1233 } 1208 1234 } 1209 1235 1210 - foreach ($fields as $field) { 1211 - $field->setViewer($viewer); 1212 - } 1236 + $constraints = $request->getValue('constraints', array()); 1237 + 1238 + $fields = $this->getSearchFieldsForConduit(); 1213 1239 1214 - $constraints = $request->getValue('constraints', array()); 1240 + foreach ($fields as $key => $field) { 1241 + if (!$field->getConduitParameterType()) { 1242 + unset($fields[$key]); 1243 + } 1244 + } 1215 1245 1216 1246 foreach ($fields as $field) { 1217 1247 if (!$field->getValueExistsInConduitRequest($constraints)) {
+11 -5
src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
··· 154 154 $table[] = "| `ids` | **IDs** | `list<int>` | {$desc_ids} |"; 155 155 $table[] = "| `phids` | **PHIDs** | `list<phid>` | {$desc_phids} |"; 156 156 foreach ($fields as $field) { 157 - $key = $field->getKeyForConduit(); 157 + $key = $field->getConduitKey(); 158 158 $label = $field->getLabel(); 159 159 160 - // TODO: Support generating and surfacing this information. 161 - $type = pht('TODO'); 162 - $description = pht('TODO'); 160 + $type_object = $field->getConduitParameterType(); 161 + if ($type_object) { 162 + $type = '`'.$type_object->getTypeName().'`'; 163 + // TODO: Support generating and surfacing this information. 164 + $description = pht('TODO'); 165 + } else { 166 + $type = ''; 167 + $description = '//'.pht('Not Supported').'//'; 168 + } 163 169 164 - $table[] = "| `{$key}` | **{$label}** | `{$type}` | {$description}"; 170 + $table[] = "| `{$key}` | **{$label}** | {$type} | {$description}"; 165 171 } 166 172 $table = implode("\n", $table); 167 173 $out[] = $table;
+4
src/applications/search/field/PhabricatorSearchCheckboxesField.php
··· 37 37 return $control; 38 38 } 39 39 40 + protected function newConduitParameterType() { 41 + return new ConduitStringListParameterType(); 42 + } 43 + 40 44 }
+1 -1
src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php
··· 38 38 return null; 39 39 } 40 40 41 - public function getKeyForConduit() { 41 + public function getConduitKey() { 42 42 return $this->getCustomField()->getModernFieldKey(); 43 43 } 44 44
+66 -15
src/applications/search/field/PhabricatorSearchField.php
··· 4 4 * @task config Configuring Fields 5 5 * @task error Handling Errors 6 6 * @task io Reading and Writing Field Values 7 + * @task conduit Integration with Conduit 7 8 * @task util Utility Methods 8 9 */ 9 10 abstract class PhabricatorSearchField extends Phobject { 10 11 11 12 private $key; 13 + private $conduitKey; 12 14 private $viewer; 13 15 private $value; 14 16 private $label; ··· 130 132 } 131 133 132 134 135 + /** 136 + * Provide an alternate field key for Conduit. 137 + * 138 + * This can allow you to choose a more usable key for API endpoints. 139 + * If no key is provided, the main key is used. 140 + * 141 + * @param string Alternate key for Conduit. 142 + * @return this 143 + * @task config 144 + */ 145 + public function setConduitKey($conduit_key) { 146 + $this->conduitKey = $conduit_key; 147 + return $this; 148 + } 149 + 150 + 151 + /** 152 + * Get the field key for use in Conduit. 153 + * 154 + * @return string Conduit key for this field. 155 + * @task config 156 + */ 157 + public function getConduitKey() { 158 + if ($this->conduitKey !== null) { 159 + return $this->conduitKey; 160 + } 161 + 162 + return $this->getKey(); 163 + } 164 + 165 + 133 166 /* -( Handling Errors )---------------------------------------------------- */ 134 167 135 168 ··· 203 236 204 237 public function getValueForQuery($value) { 205 238 return $value; 206 - } 207 - 208 - public function getValueExistsInConduitRequest(array $constraints) { 209 - return array_key_exists($this->getKey(), $constraints); 210 - } 211 - 212 - public function readValueFromConduitRequest(array $constraints) { 213 - return idx($constraints, $this->getKey()); 214 239 } 215 240 216 241 ··· 238 263 } 239 264 240 265 266 + /* -( Integration with Conduit )------------------------------------------- */ 267 + 268 + 269 + /** 270 + * @task conduit 271 + */ 272 + final public function getConduitParameterType() { 273 + $type = $this->newConduitParameterType(); 274 + 275 + if ($type) { 276 + $type->setViewer($this->getViewer()); 277 + } 278 + 279 + return $type; 280 + } 281 + 282 + protected function newConduitParameterType() { 283 + return null; 284 + } 285 + 286 + public function getValueExistsInConduitRequest(array $constraints) { 287 + return $this->getConduitParameterType()->getExists( 288 + $constraints, 289 + $this->getConduitKey()); 290 + } 291 + 292 + public function readValueFromConduitRequest(array $constraints) { 293 + return $this->getConduitParameterType()->getValue( 294 + $constraints, 295 + $this->getConduitKey()); 296 + } 297 + 298 + 241 299 /* -( Utility Methods )----------------------------------------------------- */ 242 300 243 301 ··· 270 328 } 271 329 272 330 return $list; 273 - } 274 - 275 - 276 - public function getKeyForConduit() { 277 - // TODO: This shouldn't really be different, but internal handling of 278 - // custom field keys is a bit of a mess for now. 279 - return $this->getKey(); 280 331 } 281 332 282 333
+4
src/applications/search/field/PhabricatorSearchStringListField.php
··· 19 19 return implode(', ', parent::getValueForControl()); 20 20 } 21 21 22 + protected function newConduitParameterType() { 23 + return new ConduitStringListParameterType(); 24 + } 25 + 22 26 }