-
Notifications
You must be signed in to change notification settings - Fork 937
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Moving from PHP Annotations to Attributes? Use this helpful Rector script! #1047
Comments
It does not handle nested attributes for now |
I figured out how to handle nested attributes /**
* @OA\Get(
* path="/departments",
* summary="summary",
* tags={"sit"},
- * @OA\Response(
+ * responses={@OA\Response(
* response=200,
* description="desc",
- * @OA\JsonContent(
+ * content=@OA\JsonContent(
* type="array",
* @OA\Items(ref="#/components/schemas/refff")
* ),
- * )
+ * )}
* )
*/ |
Thanks for the script @SlvrEagle23! However the ContainerConfigurator is deprecated and does not work that well with the new rector versions anymore. I have refactored the script to work with the newer rector versions https://gist.github.com/LVoogd/34078de7144663db5ab2067977516130 Beware of the sets loaded on line 54, if you don't want to update other annotations please disable these. |
i'm not able to make your script work, it does nothing when running rector. It shows 100%, then Rector is done, but nothing changed |
Hi @momala454, Make sure you use the latest version of Rector (I used 0.13.5). If that does not work maybe your notation is a bit different and therefore the script does not pick it up correctly. I did recall having to manually update some schema entities, presumably because of some special case this script does not understand. As @SlvrEagle23 stated, its a crude script and should be perceived as a good starting point. You may need to make alterations to make it work for your specific case. Good luck! |
@LVoogd i'm on 0.13.6. edit: this dummy annoation is not detected/replaced
|
@LVoogd even trying to fix only rector.php doesn't work, it does nothing <?php
use Rector\Doctrine\Set\DoctrineSetList;
use Rector\Symfony\Set\SymfonySetList;
use Rector\Symfony\Set\SensiolabsSetList;
use Rector\Nette\Set\NetteSetList;
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;
/**
* @OpenApi\Annotations\Get(
* path="/hello"
* )
*/
return function (RectorConfig $rectorConfig): void {
$rectorConfig->ruleWithConfiguration(AnnotationToAttributeRector::class, [
new AnnotationToAttribute('OpenApi\\Annotations\\AdditionalProperties', 'OpenApi\\Attributes\\AdditionalProperties'),
new AnnotationToAttribute('OpenApi\\Annotations\\Attachable', 'OpenApi\\Attributes\\Attachable'),
new AnnotationToAttribute('OpenApi\\Annotations\\Components', 'OpenApi\\Attributes\\Components'),
new AnnotationToAttribute('OpenApi\\Annotations\\Contact', 'OpenApi\\Attributes\\Contact'),
new AnnotationToAttribute('OpenApi\\Annotations\\Delete', 'OpenApi\\Attributes\\Delete'),
new AnnotationToAttribute('OpenApi\\Annotations\\Discriminator', 'OpenApi\\Attributes\\Discriminator'),
new AnnotationToAttribute('OpenApi\\Annotations\\Examples', 'OpenApi\\Attributes\\Examples'),
new AnnotationToAttribute('OpenApi\\Annotations\\ExternalDocumentation', 'OpenApi\\Attributes\\ExternalDocumentation'),
new AnnotationToAttribute('OpenApi\\Annotations\\Flow', 'OpenApi\\Attributes\\Flow'),
new AnnotationToAttribute('OpenApi\\Annotations\\Get', 'OpenApi\\Attributes\\Get'),
new AnnotationToAttribute('OpenApi\\Annotations\\Head', 'OpenApi\\Attributes\\Head'),
new AnnotationToAttribute('OpenApi\\Annotations\\Header', 'OpenApi\\Attributes\\Header'),
new AnnotationToAttribute('OpenApi\\Annotations\\Info', 'OpenApi\\Attributes\\Info'),
new AnnotationToAttribute('OpenApi\\Annotations\\Items', 'OpenApi\\Attributes\\Items'),
new AnnotationToAttribute('OpenApi\\Annotations\\JsonContent', 'OpenApi\\Attributes\\JsonContent'),
new AnnotationToAttribute('OpenApi\\Annotations\\License', 'OpenApi\\Attributes\\License'),
new AnnotationToAttribute('OpenApi\\Annotations\\Link', 'OpenApi\\Attributes\\Link'),
new AnnotationToAttribute('OpenApi\\Annotations\\MediaType', 'OpenApi\\Attributes\\MediaType'),
new AnnotationToAttribute('OpenApi\\Annotations\\OpenApi', 'OpenApi\\Attributes\\OpenApi'),
new AnnotationToAttribute('OpenApi\\Annotations\\Operation', 'OpenApi\\Attributes\\Operation'),
new AnnotationToAttribute('OpenApi\\Annotations\\Options', 'OpenApi\\Attributes\\Options'),
new AnnotationToAttribute('OpenApi\\Annotations\\Parameter', 'OpenApi\\Attributes\\Parameter'),
new AnnotationToAttribute('OpenApi\\Annotations\\Patch', 'OpenApi\\Attributes\\Patch'),
new AnnotationToAttribute('OpenApi\\Annotations\\PatchItem', 'OpenApi\\Attributes\\PatchItem'),
new AnnotationToAttribute('OpenApi\\Annotations\\PathParameter', 'OpenApi\\Attributes\\PathParameter'),
new AnnotationToAttribute('OpenApi\\Annotations\\Post', 'OpenApi\\Attributes\\Post'),
new AnnotationToAttribute('OpenApi\\Annotations\\Property', 'OpenApi\\Attributes\\Property'),
new AnnotationToAttribute('OpenApi\\Annotations\\Put', 'OpenApi\\Attributes\\Put'),
new AnnotationToAttribute('OpenApi\\Annotations\\RequestBody', 'OpenApi\\Attributes\\RequestBody'),
new AnnotationToAttribute('OpenApi\\Annotations\\Response', 'OpenApi\\Attributes\\Response'),
new AnnotationToAttribute('OpenApi\\Annotations\\Schema', 'OpenApi\\Attributes\\Schema'),
new AnnotationToAttribute('OpenApi\\Annotations\\SecurityScheme', 'OpenApi\\Attributes\\SecurityScheme'),
new AnnotationToAttribute('OpenApi\\Annotations\\Server', 'OpenApi\\Attributes\\Server'),
new AnnotationToAttribute('OpenApi\\Annotations\\ServerVariable', 'OpenApi\\Attributes\\ServerVariable'),
new AnnotationToAttribute('OpenApi\\Annotations\\Tag', 'OpenApi\\Attributes\\Tag'),
new AnnotationToAttribute('OpenApi\\Annotations\\Trace', 'OpenApi\\Attributes\\Trace'),
new AnnotationToAttribute('OpenApi\\Annotations\\Xml', 'OpenApi\\Attributes\\Xml'),
new AnnotationToAttribute('OpenApi\\Annotations\\XmlContent', 'OpenApi\\Attributes\\XmlContent'),
]);
}; php vendor/bin/rector process rector.php --dry-run --config rector.php -vvv
rector.php
[OK] Rector is done!
|
Hi @momala454 I have tested your example as follows # test.php
<?php
class test {
/**
* @OpenApi\Annotations\Get(
* path="/hello"
* )
*/
public function get()
{
}
} output:
I believe this to be the desired outcome. |
using your test.php file it gives me the same result as before for me, no changes at all. What contains your rector.php ?
@LVoogd which is your openapi library ? i'm using "zircote/swagger-php" |
@LVoogd i finally was able to make it work, however it doesn't detect usage that doesnt contains the parameter name. Like mentioned here : #1047 (comment) on the "before" of the diff It also put everything in the same line, rendering it unreadable. Is there a parameter to put one parameter per line ? Example <?php
class test {
/**
* @OpenApi\Annotations\Post(
* path="/hello",
* @OA\RequestBody(
* @OA\MediaType(
* mediaType="application/json",
* @OA\Schema(
* @OA\Property(
* property="name",
* type="string",
* description="name",
* example="My name"
* ),
* required={"name"}
* )
* )
* ),
* )
*/
public function post()
{
}
} is converted to <?php
class test {
#[\OpenApi\Attributes\Post(path: '/hello', new OA\RequestBody(new OA\MediaType(mediaType: 'application/json', new OA\Schema(new OA\Property(property: 'name', type: 'string', description: 'name', example: 'My name'), required: ['name']))))]
public function post()
{
}
} but it doesn't work because "Cannot use positional argument after named argument" the "new OA\RequestBody" is not preceded by "requestBody:" like two @OA\Response() one after the other, so i can't do like a search/replace |
Thanks @LVoogd any everyone else, these rules saved me a huge amount of time 👍 Some minor issues, which could easily be solved by adding some named parameters (those were missing in tiny percent of all migrated attributes). But all in all, this was copy & paste and done 🎉 |
@momala454 I'm having the exact same issue. Have you came up with a solution to address the named parameters? Is it even possible? |
@reksc my "solution" was a whole lot of manual replacement |
I wrote a rector rule to convert annotations to attributes: The only problem is that rector cannot indent code pretty. |
Thank you @momala454 I used a modified version of (#1047 (comment)): <?php
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;
/**
* @OA\Get(
* path="/hello"
* )
*/
return function (RectorConfig $rectorConfig): void {
$rectorConfig->ruleWithConfiguration(AnnotationToAttributeRector::class, [
new AnnotationToAttribute('OA\\AdditionalProperties', 'OpenApi\\Attributes\\AdditionalProperties'),
new AnnotationToAttribute('OA\\Attachable', 'OpenApi\\Attributes\\Attachable'),
new AnnotationToAttribute('OA\\Components', 'OpenApi\\Attributes\\Components'),
new AnnotationToAttribute('OA\\Contact', 'OpenApi\\Attributes\\Contact'),
new AnnotationToAttribute('OA\\Delete', 'OpenApi\\Attributes\\Delete'),
new AnnotationToAttribute('OA\\Discriminator', 'OpenApi\\Attributes\\Discriminator'),
new AnnotationToAttribute('OA\\Examples', 'OpenApi\\Attributes\\Examples'),
new AnnotationToAttribute('OA\\ExternalDocumentation', 'OpenApi\\Attributes\\ExternalDocumentation'),
new AnnotationToAttribute('OA\\Flow', 'OpenApi\\Attributes\\Flow'),
new AnnotationToAttribute('OA\\Get', 'OpenApi\\Attributes\\Get'),
new AnnotationToAttribute('OA\\Head', 'OpenApi\\Attributes\\Head'),
new AnnotationToAttribute('OA\\Header', 'OpenApi\\Attributes\\Header'),
new AnnotationToAttribute('OA\\Info', 'OpenApi\\Attributes\\Info'),
new AnnotationToAttribute('OA\\Items', 'OpenApi\\Attributes\\Items'),
new AnnotationToAttribute('OA\\JsonContent', 'OpenApi\\Attributes\\JsonContent'),
new AnnotationToAttribute('OA\\License', 'OpenApi\\Attributes\\License'),
new AnnotationToAttribute('OA\\Link', 'OpenApi\\Attributes\\Link'),
new AnnotationToAttribute('OA\\MediaType', 'OpenApi\\Attributes\\MediaType'),
new AnnotationToAttribute('OA\\OpenApi', 'OpenApi\\Attributes\\OpenApi'),
new AnnotationToAttribute('OA\\Operation', 'OpenApi\\Attributes\\Operation'),
new AnnotationToAttribute('OA\\Options', 'OpenApi\\Attributes\\Options'),
new AnnotationToAttribute('OA\\Parameter', 'OpenApi\\Attributes\\Parameter'),
new AnnotationToAttribute('OA\\Patch', 'OpenApi\\Attributes\\Patch'),
new AnnotationToAttribute('OA\\PatchItem', 'OpenApi\\Attributes\\PatchItem'),
new AnnotationToAttribute('OA\\PathParameter', 'OpenApi\\Attributes\\PathParameter'),
new AnnotationToAttribute('OA\\Post', 'OpenApi\\Attributes\\Post'),
new AnnotationToAttribute('OA\\Property', 'OpenApi\\Attributes\\Property'),
new AnnotationToAttribute('OA\\Put', 'OpenApi\\Attributes\\Put'),
new AnnotationToAttribute('OA\\RequestBody', 'OpenApi\\Attributes\\RequestBody'),
new AnnotationToAttribute('OA\\Response', 'OpenApi\\Attributes\\Response'),
new AnnotationToAttribute('OA\\Schema', 'OpenApi\\Attributes\\Schema'),
new AnnotationToAttribute('OA\\SecurityScheme', 'OpenApi\\Attributes\\SecurityScheme'),
new AnnotationToAttribute('OA\\Server', 'OpenApi\\Attributes\\Server'),
new AnnotationToAttribute('OA\\ServerVariable', 'OpenApi\\Attributes\\ServerVariable'),
new AnnotationToAttribute('OA\\Tag', 'OpenApi\\Attributes\\Tag'),
new AnnotationToAttribute('OA\\Trace', 'OpenApi\\Attributes\\Trace'),
new AnnotationToAttribute('OA\\Xml', 'OpenApi\\Attributes\\Xml'),
new AnnotationToAttribute('OA\\XmlContent', 'OpenApi\\Attributes\\XmlContent'),
]);
}; Because my annotations where written with the short syntax. Then I used quite a lot of vscode regex replace feature on all files, since some where quite screwed up after the migration. A lot of syntax fixed later the spec was back and running.
It does not work at all, no files where changed. Seems like it was not designed for Laravel or more diverse projects. |
Note: while the |
For me it works great #[\OpenApi\Attributes\Response(response: 200, description: 'OK', content: new \OpenApi\Attributes\JsonContent(ref: '#/components/schemas/FooBarBaz'))] |
@williamdes I know the migration works ok. But I wanted to delay the migration and just incrementally commit and deploy compatible changes to prepare for the migration in a simple and safe way. The JsonContent change isn't compatible so I can't apply this technique. |
Oh okay, I get it now |
And i will move from attributes back to annotations, because its clear to read in Editor |
If it’s for your personal projects, sure go ahead. If you’re working on code with other contributes or for a client/company I would advise against it. Since a lot of projects haven’t switched to annotations we’re still quite accustomed to them. However, these projects will become fewer over time and there will be a time that new developers have never used annotations. I can also see packages dropping support for annotations in the future. Bottom line, using annotations now can be costly in the future. You might be forced into using attributes by a package or you need to teach fresher developers this “legacy thing” that you think looks beter in the IDE. |
You can nest attributes but then you have to use new 🤷 |
Hello! Thanks for providing some light to this issue, it's going to take so many hours to manually migrate from annotations to attributes. I seem to have problems using the snippets you guys provided. I tried running rector (v1.0.1 released just a couple days ago) adapting For instance, the following annotation * @OA\Response(
* response=200,
* description="Returns all books",
* @OA\JsonContent(ref=@Model(type=BookListOutputModel::class))
* ) becomes #[OA\Response(response: 200, description: 'Returns all books', '__remove_array__')] Here is what my <?php
declare(strict_types=1);
use Rector\Config\RectorConfig;
use Rector\Doctrine\Set\DoctrineSetList;
use Rector\Symfony\Set\SymfonySetList;
use Rector\Php80\Rector\Class_\AnnotationToAttributeRector;
use Rector\Php80\ValueObject\AnnotationToAttribute;
return RectorConfig::configure()
->withPaths([
__DIR__ . '/config',
__DIR__ . '/public',
__DIR__ . '/src',
])
->withConfiguredRule(AnnotationToAttributeRector::class, [
new AnnotationToAttribute('OpenApi\\Annotations\\AdditionalProperties', 'OpenApi\\Attributes\\AdditionalProperties'),
new AnnotationToAttribute('OpenApi\\Annotations\\Attachable', 'OpenApi\\Attributes\\Attachable'),
new AnnotationToAttribute('OpenApi\\Annotations\\Components', 'OpenApi\\Attributes\\Components'),
new AnnotationToAttribute('OpenApi\\Annotations\\Contact', 'OpenApi\\Attributes\\Contact'),
new AnnotationToAttribute('OpenApi\\Annotations\\Delete', 'OpenApi\\Attributes\\Delete'),
new AnnotationToAttribute('OpenApi\\Annotations\\Discriminator', 'OpenApi\\Attributes\\Discriminator'),
new AnnotationToAttribute('OpenApi\\Annotations\\Examples', 'OpenApi\\Attributes\\Examples'),
new AnnotationToAttribute('OpenApi\\Annotations\\ExternalDocumentation', 'OpenApi\\Attributes\\ExternalDocumentation'),
new AnnotationToAttribute('OpenApi\\Annotations\\Flow', 'OpenApi\\Attributes\\Flow'),
new AnnotationToAttribute('OpenApi\\Annotations\\Get', 'OpenApi\\Attributes\\Get'),
new AnnotationToAttribute('OpenApi\\Annotations\\Head', 'OpenApi\\Attributes\\Head'),
new AnnotationToAttribute('OpenApi\\Annotations\\Header', 'OpenApi\\Attributes\\Header'),
new AnnotationToAttribute('OpenApi\\Annotations\\Info', 'OpenApi\\Attributes\\Info'),
new AnnotationToAttribute('OpenApi\\Annotations\\Items', 'OpenApi\\Attributes\\Items'),
new AnnotationToAttribute('OpenApi\\Annotations\\JsonContent', 'OpenApi\\Attributes\\JsonContent'),
new AnnotationToAttribute('OpenApi\\Annotations\\License', 'OpenApi\\Attributes\\License'),
new AnnotationToAttribute('OpenApi\\Annotations\\Link', 'OpenApi\\Attributes\\Link'),
new AnnotationToAttribute('OpenApi\\Annotations\\MediaType', 'OpenApi\\Attributes\\MediaType'),
new AnnotationToAttribute('OpenApi\\Annotations\\OpenApi', 'OpenApi\\Attributes\\OpenApi'),
new AnnotationToAttribute('OpenApi\\Annotations\\Operation', 'OpenApi\\Attributes\\Operation'),
new AnnotationToAttribute('OpenApi\\Annotations\\Options', 'OpenApi\\Attributes\\Options'),
new AnnotationToAttribute('OpenApi\\Annotations\\Parameter', 'OpenApi\\Attributes\\Parameter'),
new AnnotationToAttribute('OpenApi\\Annotations\\Patch', 'OpenApi\\Attributes\\Patch'),
new AnnotationToAttribute('OpenApi\\Annotations\\PatchItem', 'OpenApi\\Attributes\\PatchItem'),
new AnnotationToAttribute('OpenApi\\Annotations\\PathParameter', 'OpenApi\\Attributes\\PathParameter'),
new AnnotationToAttribute('OpenApi\\Annotations\\Post', 'OpenApi\\Attributes\\Post'),
new AnnotationToAttribute('OpenApi\\Annotations\\Property', 'OpenApi\\Attributes\\Property'),
new AnnotationToAttribute('OpenApi\\Annotations\\Put', 'OpenApi\\Attributes\\Put'),
new AnnotationToAttribute('OpenApi\\Annotations\\RequestBody', 'OpenApi\\Attributes\\RequestBody'),
new AnnotationToAttribute('OpenApi\\Annotations\\Response', 'OpenApi\\Attributes\\Response'),
new AnnotationToAttribute('OpenApi\\Annotations\\Schema', 'OpenApi\\Attributes\\Schema'),
new AnnotationToAttribute('OpenApi\\Annotations\\SecurityScheme', 'OpenApi\\Attributes\\SecurityScheme'),
new AnnotationToAttribute('OpenApi\\Annotations\\Server', 'OpenApi\\Attributes\\Server'),
new AnnotationToAttribute('OpenApi\\Annotations\\ServerVariable', 'OpenApi\\Attributes\\ServerVariable'),
new AnnotationToAttribute('OpenApi\\Annotations\\Tag', 'OpenApi\\Attributes\\Tag'),
new AnnotationToAttribute('OpenApi\\Annotations\\Trace', 'OpenApi\\Attributes\\Trace'),
new AnnotationToAttribute('OpenApi\\Annotations\\Xml', 'OpenApi\\Attributes\\Xml'),
new AnnotationToAttribute('OpenApi\\Annotations\\XmlContent', 'OpenApi\\Attributes\\XmlContent'),
])
->withSets([
DoctrineSetList::ANNOTATIONS_TO_ATTRIBUTES,
SymfonySetList::ANNOTATIONS_TO_ATTRIBUTES,
]); Any idea on how can I fix this? Is there a better tool or new version of the snippet that can help? Thank you! |
Maybe ask rector support directly or Stackoverflow? I had to use vscode regexes, it was not easy to fix the result |
Thanks @williamdes, I've posted this as a question on Stackoverflow and will come back here if I get any useful insights. |
Perhaps someone will find it useful. A bit of dirty code. If anyone is interested, I can put it all together into a rule. To be able to translate such code /**
* @OA\Schema(
* path="/api/data.json",
* @OA\Property(
* response="200",
* description="The data"
* )
* )
*/ To #[OA\Schema(path: '/api/data.json')]
#[OA\Property(response: '200', description: 'The data')] Using a rule in Rector that will look something like this $rectorConfig->ruleWithConfiguration(NestedAnnotationToAttributeRector::class, [
new NestedAnnotationToAttribute('OA\Schema', [
new AnnotationToAttribute('OA\Property', 'OA\Property'),
]),
] You'll need to patch a couple of files on the machine where you'll be running Rector. AttentionBefore proceeding, ensure that the files you're patching are of the same version. Usually, in local projects, versions are fixed and, as a result, not up-to-date with the latest versions of Rector and its rules. It's better to check the differences to see what will change for you. In the file NestedAnnotationToAttributeReplace on line to After this line add if ($annotationPropertyToAttributeClass instanceof AnnotationToAttribute) {
continue;
} Replace the contents of the file PhpNestedAttributeGroupFactory with this code:<?php
declare(strict_types=1);
namespace Rector\Php80\ValueObject;
use Rector\Php80\Contract\ValueObject\AnnotationToAttributeInterface;
use Rector\Validation\RectorAssert;
final class NestedAnnotationToAttribute implements AnnotationToAttributeInterface
{
/**
* @var AnnotationPropertyToAttributeClass[]|AnnotationToAttribute[]
*/
private array $annotationPropertiesToAttributeClasses = [];
/**
* @param array<string, string>|string[]|AnnotationPropertyToAttributeClass[]|AnnotationToAttribute[] $annotationPropertiesToAttributeClasses
*/
public function __construct(
private readonly string $tag,
array $annotationPropertiesToAttributeClasses,
private readonly bool $removeOriginal = false,
) {
RectorAssert::className($tag);
// back compatibility for raw scalar values
foreach ($annotationPropertiesToAttributeClasses as $annotationProperty => $attributeClass) {
if ($attributeClass instanceof AnnotationPropertyToAttributeClass || $attributeClass instanceof AnnotationToAttribute) {
$this->annotationPropertiesToAttributeClasses[] = $attributeClass;
} else {
$this->annotationPropertiesToAttributeClasses[] = new AnnotationPropertyToAttributeClass(
$attributeClass,
$annotationProperty,
);
}
}
}
public function getTag(): string
{
return $this->tag;
}
/**
* @return AnnotationPropertyToAttributeClass[]|AnnotationToAttribute[]
*/
public function getAnnotationPropertiesToAttributeClasses(): array
{
return $this->annotationPropertiesToAttributeClasses;
}
public function getAttributeClass(): string
{
return $this->tag;
}
public function shouldRemoveOriginal(): bool
{
return $this->removeOriginal;
}
public function hasExplicitParameters(): bool
{
foreach ($this->annotationPropertiesToAttributeClasses as $annotationPropertyToAttributeClass) {
if ($annotationPropertyToAttributeClass instanceof AnnotationToAttribute) {
continue;
}
if (is_string($annotationPropertyToAttributeClass->getAnnotationProperty())) {
return true;
}
}
return false;
}
} Why this solution is badWhat's needed here is nesting, specifically the ability to automatically wrap |
This isn't a bug report, but rather a helpful tool that might save folks moving from annotations to attributes a lot of time.
Rector has a tool that can automatically migrate annotations to attributes, so long as you provide the class mapping for it.
Here's a sample script I set up that (crudely) does the migration from annotations to attributes:
https://gist.github.com/BusterNeece/230f1bf619740b0564b88a69978de82c
It's not perfect, but it gets you to a really good starting point and takes care of a task that is extremely boring to do otherwise.
Edit: See below for a link to a repo that does this in an improved manner!
The text was updated successfully, but these errors were encountered: