Skip to content

Commit

Permalink
GraphQL SDL generation from Pure should handle deeply nested hierarch…
Browse files Browse the repository at this point in the history
…ies with explicit transitive implements (finos#3172)
  • Loading branch information
horbe authored Oct 23, 2024
1 parent f0df0e3 commit 5830618
Show file tree
Hide file tree
Showing 6 changed files with 139 additions and 8 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ function {doc.doc = 'Recursively get all explicitly defined generalizations.'}
meta::pure::functions::meta::hierarchicalAllGeneralizations(type : Type[1]) : Class<Any>[*]
{
let oneLevel = $type->validGeneralizations();
$oneLevel->concatenate($oneLevel->map(c | $c->validGeneralizations()))->removeDuplicates();
$oneLevel->concatenate($oneLevel->map(c | $c->hierarchicalAllGeneralizations()))->removeDuplicates();
}

function {doc.doc = 'Get all inherited properties on the provided class'}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -355,3 +355,9 @@ function <<test.Test>> meta::pure::functions::meta::tests::testTypeNameAndPath()
assertEquals( meta::pure::functions::tests::model::GeographicEntityType.CITY->typePath(),'meta::pure::functions::tests::model::GeographicEntityType');

}

function <<test.Test>> meta::pure::functions::meta::tests::testAllGeneralizations(): Boolean[1]
{
assertEquals(['Parent'], meta::pure::functions::meta::validGeneralizations(meta::pure::functions::meta::tests::packageA::Child)->map(t | $t.name));
assertEquals(['Parent', 'GrandParent', 'GreatGrandParent'], meta::pure::functions::meta::hierarchicalAllGeneralizations(meta::pure::functions::meta::tests::packageA::Child)->map(t | $t.name));
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ Class meta::pure::functions::meta::tests::packageA::ClassB

}

Class meta::pure::functions::meta::tests::packageA::Child extends meta::pure::functions::meta::tests::packageA::Parent
{

}

Class meta::pure::functions::meta::tests::packageA::Parent extends meta::pure::functions::meta::tests::packageA::GrandParent
{

}

Class meta::pure::functions::meta::tests::packageA::GrandParent extends meta::pure::functions::meta::tests::packageA::GreatGrandParent
{

}

Class meta::pure::functions::meta::tests::packageA::GreatGrandParent
{

}

function <<access.private>> meta::pure::functions::meta::tests::packageA::myFunc():Boolean[1]
{
true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ function meta::external::query::graphQL::binding::fromPure::sdl::transformPureTo
->distinct()
->filter(t | $t->instanceOf(Class));

let extendedClasses = $allTypes->map(t | $t->validGeneralizations());
let extendedClasses = $allTypes->map(t | $t->validGeneralizations())->removeDuplicates();

// Build types
let graphQLTypes = $allTypes->map(c |
Expand Down Expand Up @@ -301,11 +301,12 @@ function <<access.private>> meta::external::query::graphQL::binding::fromPure::s
let nonBuiltInScalars = $props->buildNonBuiltInGraphQLScalars($pureTypeToGraphQLScalarOverride);
let isQueryClass = $c->hasGraphQLStereotype('Query');
let temporalStereotypes = $c->getTemporalStereotypes();
let extended = if($isExtended, | [$c.name], | []);
$nonBuiltInScalars
->concatenate(
^ObjectTypeDefinition(
name = $c.name->toOne(),
implements = if($isExtended, | $c.name, | $c->validGeneralizations()->map(g | $g.name))->map(n | $n->toInterfaceTypeName()),
implements = $extended->concatenate($c->hierarchicalAllGeneralizations()->map(g | $g.name))->map(n | $n->toInterfaceTypeName()),
directives = $temporalStereotypes->map(s | temporalityToDirectives()->get($s)->toOne())->map(def | ^Directive(name = $def.name))->concatenate(buildHierarchyDirective($c, false)),
fields = $props
->map(p |
Expand Down Expand Up @@ -351,7 +352,7 @@ function <<access.private>> meta::external::query::graphQL::binding::fromPure::s
let temporalStereotypes = $c->getTemporalStereotypes();
^InterfaceTypeDefinition(
name = $c.name->toOne()->toInterfaceTypeName(),
implements = $c->validGeneralizations()->map(g | $g.name)->map(n | $n->toInterfaceTypeName()),
implements = $c->hierarchicalAllGeneralizations()->map(g | $g.name)->map(n | $n->toInterfaceTypeName()),
directives = $temporalStereotypes->map(s | temporalityToDirectives()->get($s)->toOne())->map(def | ^Directive(name = $def.name))->concatenate(buildHierarchyDirective($c, false)),
fields = $props
->map(p |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::Pers
age : Integer[1];
}

Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::NonEmployee extends meta::external::query::graphQL::binding::fromPure::sdl::tests::model::Person
{
}

Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::SelfEmployed extends meta::external::query::graphQL::binding::fromPure::sdl::tests::model::NonEmployee
{
}

Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::Contractor extends meta::external::query::graphQL::binding::fromPure::sdl::tests::model::SelfEmployed
{
}

Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::Employee extends meta::external::query::graphQL::binding::fromPure::sdl::tests::model::Person
{
title: String[1];
Expand All @@ -32,6 +44,11 @@ Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::GSEm
division: String[1];
}

Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::NonGSEmployee extends meta::external::query::graphQL::binding::fromPure::sdl::tests::model::Employee
{
company: String[1];
}

Class meta::external::query::graphQL::binding::fromPure::sdl::tests::model::Team
{
name: String[1];
Expand Down Expand Up @@ -473,7 +490,7 @@ function <<test.Test>> meta::external::query::graphQL::binding::fromPure::sdl::t
let res = typesToGraphQLString([GSEmployee, Employee, Person]);

assertEquals(
'type Employee implements EmployeeInterface @extends(class: "Person") {\n' +
'type Employee implements EmployeeInterface & PersonInterface @extends(class: "Person") {\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
' firstName: String\n' +
Expand Down Expand Up @@ -502,7 +519,7 @@ function <<test.Test>> meta::external::query::graphQL::binding::fromPure::sdl::t
' _exists: Employee_bool_exp\n' +
'}\n' +
'\n' +
'type GSEmployee implements EmployeeInterface @extends(class: "Employee") {\n' +
'type GSEmployee implements EmployeeInterface & PersonInterface @extends(class: "Employee") {\n' +
' division: String!\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
Expand Down Expand Up @@ -555,6 +572,93 @@ function <<test.Test>> meta::external::query::graphQL::binding::fromPure::sdl::t
$res);
}

function <<test.Test>> meta::external::query::graphQL::binding::fromPure::sdl::tests::testClassWithDeepMultipleInheritance():Boolean[1]
{
let res = typesToGraphQLStringWithoutDynamicFilter([GSEmployee, NonGSEmployee, Employee, Person, NonEmployee, SelfEmployed, Contractor]);

assertEquals(
'type Contractor implements SelfEmployedInterface & NonEmployeeInterface & PersonInterface @extends(class: "SelfEmployed") {\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'type Employee implements EmployeeInterface & PersonInterface @extends(class: "Person") {\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'interface EmployeeInterface implements PersonInterface {\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'type GSEmployee implements EmployeeInterface & PersonInterface @extends(class: "Employee") {\n' +
' division: String!\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'type NonEmployee implements NonEmployeeInterface & PersonInterface @extends(class: "Person") {\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'interface NonEmployeeInterface implements PersonInterface {\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'type NonGSEmployee implements EmployeeInterface & PersonInterface @extends(class: "Employee") {\n' +
' company: String!\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'type Person implements PersonInterface {\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'interface PersonInterface {\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'type SelfEmployed implements SelfEmployedInterface & NonEmployeeInterface & PersonInterface @extends(class: "NonEmployee") {\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'interface SelfEmployedInterface implements NonEmployeeInterface & PersonInterface {\n' +
' firstName: String\n' +
' lastName: String!\n' +
' age: Int!\n' +
'}\n' +
'\n' +
'scalar StrictDate\n' +
'\n' +
'directive @extends(class: String!) on OBJECT | INPUT_OBJECT',
$res);
}

function <<test.Test>> meta::external::query::graphQL::binding::fromPure::sdl::tests::testPropertyWithInheritance():Boolean[1]
{
let res = typesToGraphQLString([Team, Employee, Person]);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,7 @@ function <<test.Test>> meta::external::query::graphQL::binding::toPure::sdl::tes
let pureTypes =
graphQLToPure(
'#GQL{' +
'type Employee implements EmployeeInterface @extends(class: "Person") {\n' +
'type Employee implements EmployeeInterface & PersonInterface @extends(class: "Person") {\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
' firstName: String\n' +
Expand All @@ -398,7 +398,7 @@ function <<test.Test>> meta::external::query::graphQL::binding::toPure::sdl::tes
' age: Int!\n' +
'}\n' +
'\n' +
'type GSEmployee implements EmployeeInterface @extends(class: "Employee") {\n' +
'type GSEmployee implements EmployeeInterface & PersonInterface @extends(class: "Employee") {\n' +
' division: String!\n' +
' title: String!\n' +
' startDate: StrictDate!\n' +
Expand Down

0 comments on commit 5830618

Please sign in to comment.