diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -231,14 +231,18 @@ 'ConduitException' => 'applications/conduit/protocol/exception/ConduitException.php', 'ConduitGetCapabilitiesConduitAPIMethod' => 'applications/conduit/method/ConduitGetCapabilitiesConduitAPIMethod.php', 'ConduitGetCertificateConduitAPIMethod' => 'applications/conduit/method/ConduitGetCertificateConduitAPIMethod.php', + 'ConduitListParameterType' => 'applications/conduit/parametertype/ConduitListParameterType.php', 'ConduitLogGarbageCollector' => 'applications/conduit/garbagecollector/ConduitLogGarbageCollector.php', 'ConduitMethodDoesNotExistException' => 'applications/conduit/protocol/exception/ConduitMethodDoesNotExistException.php', 'ConduitMethodNotFoundException' => 'applications/conduit/protocol/exception/ConduitMethodNotFoundException.php', + 'ConduitParameterType' => 'applications/conduit/parametertype/ConduitParameterType.php', 'ConduitPingConduitAPIMethod' => 'applications/conduit/method/ConduitPingConduitAPIMethod.php', 'ConduitQueryConduitAPIMethod' => 'applications/conduit/method/ConduitQueryConduitAPIMethod.php', 'ConduitResultSearchEngineExtension' => 'applications/conduit/query/ConduitResultSearchEngineExtension.php', 'ConduitSSHWorkflow' => 'applications/conduit/ssh/ConduitSSHWorkflow.php', + 'ConduitStringListParameterType' => 'applications/conduit/parametertype/ConduitStringListParameterType.php', 'ConduitTokenGarbageCollector' => 'applications/conduit/garbagecollector/ConduitTokenGarbageCollector.php', + 'ConduitUserListParameterType' => 'applications/conduit/parametertype/ConduitUserListParameterType.php', 'ConpherenceColumnViewController' => 'applications/conpherence/controller/ConpherenceColumnViewController.php', 'ConpherenceConduitAPIMethod' => 'applications/conpherence/conduit/ConpherenceConduitAPIMethod.php', 'ConpherenceConfigOptions' => 'applications/conpherence/config/ConpherenceConfigOptions.php', @@ -4071,14 +4075,18 @@ 'ConduitException' => 'Exception', 'ConduitGetCapabilitiesConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitGetCertificateConduitAPIMethod' => 'ConduitAPIMethod', + 'ConduitListParameterType' => 'ConduitParameterType', 'ConduitLogGarbageCollector' => 'PhabricatorGarbageCollector', 'ConduitMethodDoesNotExistException' => 'ConduitMethodNotFoundException', 'ConduitMethodNotFoundException' => 'ConduitException', + 'ConduitParameterType' => 'Phobject', 'ConduitPingConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitQueryConduitAPIMethod' => 'ConduitAPIMethod', 'ConduitResultSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'ConduitSSHWorkflow' => 'PhabricatorSSHWorkflow', + 'ConduitStringListParameterType' => 'ConduitListParameterType', 'ConduitTokenGarbageCollector' => 'PhabricatorGarbageCollector', + 'ConduitUserListParameterType' => 'ConduitListParameterType', 'ConpherenceColumnViewController' => 'ConpherenceController', 'ConpherenceConduitAPIMethod' => 'ConduitAPIMethod', 'ConpherenceConfigOptions' => 'PhabricatorApplicationConfigOptions', diff --git a/src/applications/conduit/parametertype/ConduitListParameterType.php b/src/applications/conduit/parametertype/ConduitListParameterType.php new file mode 100644 --- /dev/null +++ b/src/applications/conduit/parametertype/ConduitListParameterType.php @@ -0,0 +1,37 @@ +raiseValidationException( + $request, + $key, + pht('Expected a list, but value is not a list.')); + } + + $actual_keys = array_keys($value); + if ($value) { + $natural_keys = range(0, count($value) - 1); + } else { + $natural_keys = array(); + } + + if ($actual_keys !== $natural_keys) { + $this->raiseValidationException( + $request, + $key, + pht('Expected a list, but value is an object.')); + } + + return $value; + } + + protected function getParameterDefault() { + return array(); + } + +} diff --git a/src/applications/conduit/parametertype/ConduitParameterType.php b/src/applications/conduit/parametertype/ConduitParameterType.php new file mode 100644 --- /dev/null +++ b/src/applications/conduit/parametertype/ConduitParameterType.php @@ -0,0 +1,97 @@ +viewer = $viewer; + return $this; + } + + + final public function getViewer() { + if (!$this->viewer) { + throw new PhutilInvalidStateException('setViewer'); + } + return $this->viewer; + } + + + final public function getExists(array $request, $key) { + return $this->getValueExists($request, $key); + } + + + final public function getValue(array $request, $key) { + if (!$this->getExists($request, $key)) { + return $this->getParameterDefault(); + } + + return $this->getParameterValue($request, $key); + } + + + final public function getDefaultValue() { + return $this->getParameterDefault(); + } + + + final public function getTypeName() { + return $this->getParameterTypeName(); + } + + + final public function getFormatDescriptions() { + return $this->getParameterFormatDescriptions(); + } + + + final public function getExamples() { + return $this->getParameterExamples(); + } + + protected function raiseValidationException(array $request, $key, $message) { + // TODO: Specialize this so we can give users more tailored messages from + // Conduit. + throw new Exception($message); + } + + + final public static function getAllTypes() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getTypeName') + ->setSortMethod('getTypeName') + ->execute(); + } + + + protected function getParameterExists(array $request, $key) { + return array_key_exists($key, $request); + } + + protected function getParameterValue(array $request, $key) { + return $request[$key]; + } + + abstract protected function getParameterTypeName(); + + + abstract protected function getParameterFormatDescriptions(); + + + abstract protected function getParameterExamples(); + + protected function getParameterDefault() { + return null; + } + +} diff --git a/src/applications/conduit/parametertype/ConduitStringListParameterType.php b/src/applications/conduit/parametertype/ConduitStringListParameterType.php new file mode 100644 --- /dev/null +++ b/src/applications/conduit/parametertype/ConduitStringListParameterType.php @@ -0,0 +1,40 @@ + $item) { + if (!is_string($item)) { + $this->raiseValidationException( + $request, + $key, + pht( + 'Expected a list of strings, but item with index "%s" is '. + 'not a string.', + $idx)); + } + } + + return $list; + } + + protected function getParameterTypeName() { + return 'list'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('List of strings.'), + ); + } + + protected function getParameterExamples() { + return array( + '["mango", "nectarine"]', + ); + } + +} diff --git a/src/applications/conduit/parametertype/ConduitUserListParameterType.php b/src/applications/conduit/parametertype/ConduitUserListParameterType.php new file mode 100644 --- /dev/null +++ b/src/applications/conduit/parametertype/ConduitUserListParameterType.php @@ -0,0 +1,33 @@ +setViewer($this->getViewer()) + ->resolvePHIDs($list); + } + + protected function getParameterTypeName() { + return 'list'; + } + + protected function getParameterFormatDescriptions() { + return array( + pht('List of user PHIDs.'), + pht('List of usernames.'), + pht('List with a mixture of PHIDs and usernames.'), + ); + } + + protected function getParameterExamples() { + return array( + '["PHID-USER-1111"]', + '["alincoln"]', + '["PHID-USER-2222", "alincoln"]', + ); + } + +} diff --git a/src/applications/conduit/protocol/ConduitAPIRequest.php b/src/applications/conduit/protocol/ConduitAPIRequest.php --- a/src/applications/conduit/protocol/ConduitAPIRequest.php +++ b/src/applications/conduit/protocol/ConduitAPIRequest.php @@ -14,6 +14,10 @@ return coalesce(idx($this->params, $key), $default); } + public function getValueExists($key) { + return array_key_exists($key, $this->params); + } + public function getAllParameters() { return $this->params; } diff --git a/src/applications/paste/query/PhabricatorPasteSearchEngine.php b/src/applications/paste/query/PhabricatorPasteSearchEngine.php --- a/src/applications/paste/query/PhabricatorPasteSearchEngine.php +++ b/src/applications/paste/query/PhabricatorPasteSearchEngine.php @@ -47,6 +47,7 @@ id(new PhabricatorUsersSearchField()) ->setAliases(array('authors')) ->setKey('authorPHIDs') + ->setConduitKey('authors') ->setLabel(pht('Authors')), id(new PhabricatorSearchStringListField()) ->setKey('languages') diff --git a/src/applications/people/searchfield/PhabricatorUsersSearchField.php b/src/applications/people/searchfield/PhabricatorUsersSearchField.php --- a/src/applications/people/searchfield/PhabricatorUsersSearchField.php +++ b/src/applications/people/searchfield/PhabricatorUsersSearchField.php @@ -15,4 +15,8 @@ return new PhabricatorPeopleUserFunctionDatasource(); } + protected function newConduitParameterType() { + return new ConduitUserListParameterType(); + } + } diff --git a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php --- a/src/applications/search/engine/PhabricatorApplicationSearchEngine.php +++ b/src/applications/search/engine/PhabricatorApplicationSearchEngine.php @@ -1173,7 +1173,34 @@ } public function getSearchFieldsForConduit() { - $fields = $this->buildSearchFields(); + $standard_fields = $this->buildSearchFields(); + + $fields = array(); + foreach ($standard_fields as $field_key => $field) { + $conduit_key = $field->getConduitKey(); + + if (isset($fields[$conduit_key])) { + $other = $fields[$conduit_key]; + $other_key = $other->getKey(); + + throw new Exception( + pht( + 'SearchFields "%s" (of class "%s") and "%s" (of class "%s") both '. + 'define the same Conduit key ("%s"). Keys must be unique.', + $field_key, + get_class($field), + $other_key, + get_class($other), + $conduit_key)); + } + + $fields[$conduit_key] = $field; + } + + $viewer = $this->requireViewer(); + foreach ($fields as $key => $field) { + $field->setViewer($viewer); + } // These are handled separately for Conduit, so don't show them as // supported. @@ -1187,7 +1214,6 @@ public function buildConduitResponse(ConduitAPIRequest $request) { $viewer = $this->requireViewer(); - $fields = $this->buildSearchFields(); $query_key = $request->getValue('queryKey'); if (!strlen($query_key)) { @@ -1207,12 +1233,16 @@ } } - foreach ($fields as $field) { - $field->setViewer($viewer); - } - $constraints = $request->getValue('constraints', array()); + $fields = $this->getSearchFieldsForConduit(); + + foreach ($fields as $key => $field) { + if (!$field->getConduitParameterType()) { + unset($fields[$key]); + } + } + foreach ($fields as $field) { if (!$field->getValueExistsInConduitRequest($constraints)) { continue; diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php --- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php +++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php @@ -154,14 +154,20 @@ $table[] = "| `ids` | **IDs** | `list` | {$desc_ids} |"; $table[] = "| `phids` | **PHIDs** | `list` | {$desc_phids} |"; foreach ($fields as $field) { - $key = $field->getKeyForConduit(); + $key = $field->getConduitKey(); $label = $field->getLabel(); - // TODO: Support generating and surfacing this information. - $type = pht('TODO'); - $description = pht('TODO'); + $type_object = $field->getConduitParameterType(); + if ($type_object) { + $type = '`'.$type_object->getTypeName().'`'; + // TODO: Support generating and surfacing this information. + $description = pht('TODO'); + } else { + $type = ''; + $description = '//'.pht('Not Supported').'//'; + } - $table[] = "| `{$key}` | **{$label}** | `{$type}` | {$description}"; + $table[] = "| `{$key}` | **{$label}** | {$type} | {$description}"; } $table = implode("\n", $table); $out[] = $table; diff --git a/src/applications/search/field/PhabricatorSearchCheckboxesField.php b/src/applications/search/field/PhabricatorSearchCheckboxesField.php --- a/src/applications/search/field/PhabricatorSearchCheckboxesField.php +++ b/src/applications/search/field/PhabricatorSearchCheckboxesField.php @@ -37,4 +37,8 @@ return $control; } + protected function newConduitParameterType() { + return new ConduitStringListParameterType(); + } + } diff --git a/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php b/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php --- a/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php +++ b/src/applications/search/field/PhabricatorSearchCustomFieldProxyField.php @@ -38,7 +38,7 @@ return null; } - public function getKeyForConduit() { + public function getConduitKey() { return $this->getCustomField()->getModernFieldKey(); } diff --git a/src/applications/search/field/PhabricatorSearchField.php b/src/applications/search/field/PhabricatorSearchField.php --- a/src/applications/search/field/PhabricatorSearchField.php +++ b/src/applications/search/field/PhabricatorSearchField.php @@ -4,11 +4,13 @@ * @task config Configuring Fields * @task error Handling Errors * @task io Reading and Writing Field Values + * @task conduit Integration with Conduit * @task util Utility Methods */ abstract class PhabricatorSearchField extends Phobject { private $key; + private $conduitKey; private $viewer; private $value; private $label; @@ -130,6 +132,37 @@ } + /** + * Provide an alternate field key for Conduit. + * + * This can allow you to choose a more usable key for API endpoints. + * If no key is provided, the main key is used. + * + * @param string Alternate key for Conduit. + * @return this + * @task config + */ + public function setConduitKey($conduit_key) { + $this->conduitKey = $conduit_key; + return $this; + } + + + /** + * Get the field key for use in Conduit. + * + * @return string Conduit key for this field. + * @task config + */ + public function getConduitKey() { + if ($this->conduitKey !== null) { + return $this->conduitKey; + } + + return $this->getKey(); + } + + /* -( Handling Errors )---------------------------------------------------- */ @@ -205,14 +238,6 @@ return $value; } - public function getValueExistsInConduitRequest(array $constraints) { - return array_key_exists($this->getKey(), $constraints); - } - - public function readValueFromConduitRequest(array $constraints) { - return idx($constraints, $this->getKey()); - } - /* -( Rendering Controls )------------------------------------------------- */ @@ -238,6 +263,39 @@ } +/* -( Integration with Conduit )------------------------------------------- */ + + + /** + * @task conduit + */ + final public function getConduitParameterType() { + $type = $this->newConduitParameterType(); + + if ($type) { + $type->setViewer($this->getViewer()); + } + + return $type; + } + + protected function newConduitParameterType() { + return null; + } + + public function getValueExistsInConduitRequest(array $constraints) { + return $this->getConduitParameterType()->getExists( + $constraints, + $this->getConduitKey()); + } + + public function readValueFromConduitRequest(array $constraints) { + return $this->getConduitParameterType()->getValue( + $constraints, + $this->getConduitKey()); + } + + /* -( Utility Methods )----------------------------------------------------- */ @@ -273,12 +331,5 @@ } - public function getKeyForConduit() { - // TODO: This shouldn't really be different, but internal handling of - // custom field keys is a bit of a mess for now. - return $this->getKey(); - } - - } diff --git a/src/applications/search/field/PhabricatorSearchStringListField.php b/src/applications/search/field/PhabricatorSearchStringListField.php --- a/src/applications/search/field/PhabricatorSearchStringListField.php +++ b/src/applications/search/field/PhabricatorSearchStringListField.php @@ -19,4 +19,8 @@ return implode(', ', parent::getValueForControl()); } + protected function newConduitParameterType() { + return new ConduitStringListParameterType(); + } + }