-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Add graphql schema diff to feature branch. * Add back services printer. * Total rewrite started. * Someday I will stop re-writing this feature... But it is not today. * More things. * This almost works now. * Splitting out and making non-interactive work. * Fix schema comparator. * Add schema diff command. * Implement interfaces. * Remove unused use. * Allow extends as interfaces and map inputs correctly. * Fixing updates. * Fix issues with diffing fields and args. * Removing field improvements. * Fix issue with updating types. * Fix input type diff detection. * Update apple. * Remove the services printer. * Remove unused function. * Adding argument and value diff tests. * Add tests for enum and schema. * More comparator tests. * Testing the new command. * Remove unused tests for now. * Adding tests for graphql code helper. * Additional tests.
- Loading branch information
1 parent
6507b1b
commit 2f7c32b
Showing
40 changed files
with
4,238 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ForestCityLabs\Framework\Command; | ||
|
||
use GraphQL\Language\Parser; | ||
use GraphQL\Type\Schema; | ||
use GraphQL\Utils\BuildSchema; | ||
use GraphQL\Utils\SchemaPrinter; | ||
use SebastianBergmann\Diff\Differ; | ||
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; | ||
use Symfony\Component\Console\Command\Command; | ||
use Symfony\Component\Console\Input\InputInterface; | ||
use Symfony\Component\Console\Output\OutputInterface; | ||
|
||
class GraphQLSchemaDiffCommand extends Command | ||
{ | ||
public function __construct( | ||
private string $schema_file, | ||
private Schema $schema, | ||
) { | ||
parent::__construct('graphql:schema-diff'); | ||
} | ||
|
||
protected function execute(InputInterface $input, OutputInterface $output): int | ||
{ | ||
// The desired schema is loaded from a graphql file. | ||
$new = BuildSchema::build( | ||
Parser::parse(file_get_contents($this->schema_file)) | ||
); | ||
|
||
// The current schema is found in code. | ||
$old = $this->schema; | ||
|
||
$differ = new Differ(new UnifiedDiffOutputBuilder()); | ||
$opts = ['sortArguments' => true, 'sortEnumValues' => true, 'sortFields' => true, 'sortInputFields' => true, 'sortTypes' => true]; | ||
$diff = $differ->diff( | ||
SchemaPrinter::doPrint($old, $opts), | ||
SchemaPrinter::doPrint($new, $opts), | ||
); | ||
|
||
foreach (preg_split("/\r\n|\n|\r/", $diff) as $line) { | ||
if (substr($line, 0, 1) === '-') { | ||
$output->writeln('<fg=red>' . $line . '</>'); | ||
} elseif (substr($line, 0, 1) === '+') { | ||
$output->writeln('<fg=green>' . $line . '</>'); | ||
} else { | ||
$output->writeln($line); | ||
} | ||
} | ||
|
||
return Command::SUCCESS; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,91 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ForestCityLabs\Framework\GraphQL\Diff; | ||
|
||
use GraphQL\Type\Definition\Argument; | ||
use GraphQL\Type\Definition\ListOfType; | ||
use GraphQL\Type\Definition\NonNull; | ||
use GraphQL\Type\Definition\Type; | ||
use GraphQL\Type\Definition\WrappingType; | ||
|
||
class ArgumentDiff | ||
{ | ||
public function __construct( | ||
private readonly Argument $old_argument, | ||
private readonly Argument $new_argument | ||
) { | ||
} | ||
|
||
public function getOldArgument(): Argument | ||
{ | ||
return $this->old_argument; | ||
} | ||
|
||
public function getNewArgument(): Argument | ||
{ | ||
return $this->new_argument; | ||
} | ||
|
||
public function isNameDifferent(): bool | ||
{ | ||
return $this->old_argument->name !== $this->new_argument->name; | ||
} | ||
|
||
public function isDescriptionDifferent(): bool | ||
{ | ||
return $this->old_argument->description !== $this->new_argument->description; | ||
} | ||
public function isTypeDifferent(): bool | ||
{ | ||
$old_type = Type::getNamedType($this->old_argument->getType()); | ||
$new_type = Type::getNamedType($this->new_argument->getType()); | ||
return $old_type->name !== $new_type->name; | ||
} | ||
|
||
public function isListDifferent(): bool | ||
{ | ||
$old_list = false; | ||
$new_list = false; | ||
$old_type = $this->old_argument->getType(); | ||
$new_type = $this->new_argument->getType(); | ||
while ($old_type instanceof WrappingType) { | ||
if ($old_type instanceof ListOfType) { | ||
$old_list = true; | ||
} | ||
$old_type = $old_type->getWrappedType(); | ||
} | ||
while ($new_type instanceof WrappingType) { | ||
if ($new_type instanceof ListOfType) { | ||
$new_list = true; | ||
} | ||
$new_type = $new_type->getWrappedType(); | ||
} | ||
return $new_list !== $old_list; | ||
} | ||
|
||
public function isNonNullDifferent(): bool | ||
{ | ||
$old_non_null = false; | ||
$new_non_null = false; | ||
$old_type = $this->old_argument->getType(); | ||
$new_type = $this->new_argument->getType(); | ||
if ($old_type instanceof NonNull) { | ||
$old_non_null = true; | ||
} | ||
if ($new_type instanceof NonNull) { | ||
$new_non_null = true; | ||
} | ||
return $new_non_null !== $old_non_null; | ||
} | ||
|
||
public function isDifferent(): bool | ||
{ | ||
return ($this->isNameDifferent() | ||
|| $this->isDescriptionDifferent() | ||
|| $this->isTypeDifferent() | ||
|| $this->isListDifferent() | ||
|| $this->isNonNullDifferent()); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,64 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ForestCityLabs\Framework\GraphQL\Diff; | ||
|
||
use GraphQL\Type\Definition\EnumType; | ||
use GraphQL\Type\Definition\NamedType; | ||
|
||
class EnumTypeDiff implements TypeDiff | ||
{ | ||
public function __construct( | ||
private readonly EnumType $old_enum, | ||
private readonly EnumType $new_enum, | ||
private readonly array $new_values, | ||
private readonly array $altered_values, | ||
private readonly array $dropped_values | ||
) { | ||
} | ||
|
||
public function isNameDifferent(): bool | ||
{ | ||
return $this->old_enum->name() !== $this->new_enum->name(); | ||
} | ||
|
||
public function isDescriptionDifferent(): bool | ||
{ | ||
return $this->old_enum->description() !== $this->new_enum->description(); | ||
} | ||
|
||
public function getOldType(): NamedType | ||
{ | ||
return $this->old_enum; | ||
} | ||
|
||
public function getNewType(): NamedType | ||
{ | ||
return $this->new_enum; | ||
} | ||
|
||
public function getNewValues(): array | ||
{ | ||
return $this->new_values; | ||
} | ||
|
||
public function getAlteredValues(): array | ||
{ | ||
return $this->altered_values; | ||
} | ||
|
||
public function getDroppedValues(): array | ||
{ | ||
return $this->dropped_values; | ||
} | ||
|
||
public function isDifferent(): bool | ||
{ | ||
return ($this->isNameDifferent() | ||
|| $this->isDescriptionDifferent() | ||
|| count($this->new_values) > 0 | ||
|| count($this->altered_values) > 0 | ||
|| count($this->dropped_values) > 0); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,119 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace ForestCityLabs\Framework\GraphQL\Diff; | ||
|
||
use GraphQL\Type\Definition\FieldDefinition; | ||
use GraphQL\Type\Definition\ListOfType; | ||
use GraphQL\Type\Definition\NonNull; | ||
use GraphQL\Type\Definition\Type; | ||
use GraphQL\Type\Definition\WrappingType; | ||
|
||
class FieldDiff | ||
{ | ||
public function __construct( | ||
private readonly FieldDefinition $old_field, | ||
private readonly FieldDefinition $new_field, | ||
private readonly array $new_arguments, | ||
private readonly array $altered_arguments, | ||
private readonly array $dropped_arguments | ||
) { | ||
} | ||
|
||
public function getOldField(): FieldDefinition | ||
{ | ||
return $this->old_field; | ||
} | ||
|
||
public function getNewField(): FieldDefinition | ||
{ | ||
return $this->new_field; | ||
} | ||
|
||
public function getNewArguments(): array | ||
{ | ||
return $this->new_arguments; | ||
} | ||
|
||
public function getAlteredArguments(): array | ||
{ | ||
return $this->altered_arguments; | ||
} | ||
|
||
public function getDroppedArguments(): array | ||
{ | ||
return $this->dropped_arguments; | ||
} | ||
|
||
public function isNameDifferent(): bool | ||
{ | ||
return $this->old_field->getName() !== $this->new_field->getName(); | ||
} | ||
|
||
public function isDescriptionDifferent(): bool | ||
{ | ||
return $this->old_field->description !== $this->new_field->description; | ||
} | ||
|
||
public function isTypeDifferent(): bool | ||
{ | ||
$old_type = Type::getNamedType($this->old_field->getType()); | ||
$new_type = Type::getNamedType($this->new_field->getType()); | ||
return $old_type->name !== $new_type->name; | ||
} | ||
|
||
public function isListDifferent(): bool | ||
{ | ||
$old_list = false; | ||
$new_list = false; | ||
$old_type = $this->old_field->getType(); | ||
$new_type = $this->new_field->getType(); | ||
while ($old_type instanceof WrappingType) { | ||
if ($old_type instanceof ListOfType) { | ||
$old_list = true; | ||
} | ||
$old_type = $old_type->getWrappedType(); | ||
} | ||
while ($new_type instanceof WrappingType) { | ||
if ($new_type instanceof ListOfType) { | ||
$new_list = true; | ||
} | ||
$new_type = $new_type->getWrappedType(); | ||
} | ||
return $new_list !== $old_list; | ||
} | ||
|
||
public function isNonNullDifferent(): bool | ||
{ | ||
$old_non_null = false; | ||
$new_non_null = false; | ||
$old_type = $this->old_field->getType(); | ||
$new_type = $this->new_field->getType(); | ||
if ($old_type instanceof NonNull) { | ||
$old_non_null = true; | ||
} | ||
if ($new_type instanceof NonNull) { | ||
$new_non_null = true; | ||
} | ||
return $new_non_null !== $old_non_null; | ||
} | ||
|
||
public function isDeprecationReasonDifferent(): bool | ||
{ | ||
return $this->old_field->deprecationReason !== $this->new_field->deprecationReason; | ||
} | ||
|
||
public function isDifferent(): bool | ||
{ | ||
return ($this->isNameDifferent() | ||
|| $this->isDescriptionDifferent() | ||
|| $this->isDeprecationReasonDifferent() | ||
|| $this->isTypeDifferent() | ||
|| $this->isListDifferent() | ||
|| $this->isNonNullDifferent() | ||
|| count($this->new_arguments) > 0 | ||
|| count($this->altered_arguments) > 0 | ||
|| count($this->dropped_arguments) > 0); | ||
} | ||
} |
Oops, something went wrong.