@@ -3,6 +3,8 @@ package graphql.schema
33import graphql.AssertException
44import graphql.Scalars
55import graphql.introspection.Introspection
6+ import graphql.language.EnumValue
7+ import graphql.schema.validation.InvalidSchemaException
68import spock.lang.Specification
79
810import static graphql.Scalars.GraphQLString
@@ -11,6 +13,7 @@ import static graphql.schema.GraphQLDirective.newDirective
1113import static graphql.schema.GraphQLAppliedDirective.newDirective as newAppliedDirective
1214import static graphql.schema.GraphQLAppliedDirectiveArgument.newArgument as newAppliedArgument
1315import static graphql.schema.GraphQLEnumType.newEnum
16+ import static graphql.schema.GraphQLEnumValueDefinition.newEnumValueDefinition
1417import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition
1518import static graphql.schema.GraphQLInputObjectField.newInputObjectField
1619import static graphql.schema.GraphQLInputObjectType.newInputObject
@@ -2108,4 +2111,261 @@ class FastBuilderTest extends Specification {
21082111 def searchField = schema. queryType. getFieldDefinition(" search" )
21092112 searchField. getArgument(" filter" ). getType() == filterInput
21102113 }
2114+
2115+ def " applied directive on directive definition argument with type-ref enum arg resolves correctly" () {
2116+ given : " an enum type used as the applied directive's argument type"
2117+ def colorEnum = newEnum()
2118+ .name(" Color" )
2119+ .value(" RED" )
2120+ .value(" GREEN" )
2121+ .build()
2122+
2123+ and : " a helper directive whose arg type is Color — applied to directive definition args"
2124+ def annotateDirective = newDirective()
2125+ .name(" annotate" )
2126+ .validLocation(Introspection.DirectiveLocation . ARGUMENT_DEFINITION )
2127+ .argument(newArgument()
2128+ .name(" color" )
2129+ .type(typeRef(" Color" )))
2130+ .build()
2131+
2132+ and : " an applied @annotate on a directive definition argument, with unresolved type ref"
2133+ def appliedAnnotate = newAppliedDirective()
2134+ .name(" annotate" )
2135+ .argument(newAppliedArgument()
2136+ .name(" color" )
2137+ .type(typeRef(" Color" ))
2138+ .valueLiteral(new EnumValue (" GREEN" ))
2139+ .build())
2140+ .build()
2141+
2142+ and : " a main directive whose argument carries the applied @annotate"
2143+ def mainDirective = newDirective()
2144+ .name(" main" )
2145+ .validLocation(Introspection.DirectiveLocation . FIELD_DEFINITION )
2146+ .argument(newArgument()
2147+ .name(" arg" )
2148+ .type(GraphQLString )
2149+ .withAppliedDirective(appliedAnnotate))
2150+ .build()
2151+
2152+ and : " a simple query type"
2153+ def queryType = newObject()
2154+ .name(" Query" )
2155+ .field(newFieldDefinition()
2156+ .name(" field" )
2157+ .type(GraphQLString ))
2158+ .build()
2159+
2160+ when : " building with FastBuilder and validation enabled"
2161+ def schema = new GraphQLSchema.FastBuilder (
2162+ GraphQLCodeRegistry . newCodeRegistry(), queryType, null , null )
2163+ .addType(colorEnum)
2164+ .additionalDirective(annotateDirective)
2165+ .additionalDirective(mainDirective)
2166+ .withValidation(true )
2167+ .build()
2168+
2169+ then : " schema builds successfully and the applied directive arg type is resolved"
2170+ schema != null
2171+ def resolvedMain = schema. getDirective(" main" )
2172+ def mainArg = resolvedMain. getArgument(" arg" )
2173+ def resolvedApplied = mainArg. getAppliedDirective(" annotate" )
2174+ resolvedApplied != null
2175+ ! (resolvedApplied. getArgument(" color" ). type instanceof GraphQLTypeReference )
2176+ resolvedApplied. getArgument(" color" ). type == colorEnum
2177+ }
2178+
2179+ // Regression tests for ShallowTypeRefCollector bug:
2180+ // Applied directives on field arguments and enum values were not scanned for type
2181+ // references, leaving GraphQLTypeReference unresolved and causing spurious
2182+ // InvalidSchemaException from AppliedDirectiveArgumentsAreValid validator.
2183+
2184+ def " applied directive on object type field argument with type-ref enum arg resolves correctly" () {
2185+ given : " an enum type used as a directive argument type"
2186+ def statusEnum = newEnum()
2187+ .name(" Status" )
2188+ .value(" ACTIVE" )
2189+ .value(" INACTIVE" )
2190+ .build()
2191+
2192+ and : " a directive definition referencing Status"
2193+ def metaDirective = newDirective()
2194+ .name(" meta" )
2195+ .validLocation(Introspection.DirectiveLocation . ARGUMENT_DEFINITION )
2196+ .argument(newArgument()
2197+ .name(" status" )
2198+ .type(typeRef(" Status" )))
2199+ .build()
2200+
2201+ and : " an applied directive on a field argument whose arg type is still a GraphQLTypeReference"
2202+ def appliedMeta = newAppliedDirective()
2203+ .name(" meta" )
2204+ .argument(newAppliedArgument()
2205+ .name(" status" )
2206+ .type(typeRef(" Status" ))
2207+ .valueLiteral(new EnumValue (" ACTIVE" ))
2208+ .build())
2209+ .build()
2210+
2211+ and : " query type with a field whose argument carries the applied directive"
2212+ def queryType = newObject()
2213+ .name(" Query" )
2214+ .field(newFieldDefinition()
2215+ .name(" field" )
2216+ .type(GraphQLString )
2217+ .argument(newArgument()
2218+ .name(" arg" )
2219+ .type(GraphQLString )
2220+ .withAppliedDirective(appliedMeta)))
2221+ .build()
2222+
2223+ when : " building with FastBuilder and validation enabled"
2224+ def schema = new GraphQLSchema.FastBuilder (
2225+ GraphQLCodeRegistry . newCodeRegistry(), queryType, null , null )
2226+ .addType(statusEnum)
2227+ .additionalDirective(metaDirective)
2228+ .withValidation(true )
2229+ .build()
2230+
2231+ then : " schema builds successfully and the applied directive arg type is resolved"
2232+ schema != null
2233+ def fieldArg = schema. queryType. getFieldDefinition(" field" ). getArgument(" arg" )
2234+ def resolvedApplied = fieldArg. getAppliedDirective(" meta" )
2235+ resolvedApplied != null
2236+ ! (resolvedApplied. getArgument(" status" ). type instanceof GraphQLTypeReference )
2237+ resolvedApplied. getArgument(" status" ). type == statusEnum
2238+ }
2239+
2240+ def " applied directive on interface field argument with type-ref enum arg resolves correctly" () {
2241+ given : " an enum type used as a directive argument type"
2242+ def statusEnum = newEnum()
2243+ .name(" Status" )
2244+ .value(" ACTIVE" )
2245+ .value(" INACTIVE" )
2246+ .build()
2247+
2248+ and : " a directive definition referencing Status"
2249+ def metaDirective = newDirective()
2250+ .name(" meta" )
2251+ .validLocation(Introspection.DirectiveLocation . ARGUMENT_DEFINITION )
2252+ .argument(newArgument()
2253+ .name(" status" )
2254+ .type(typeRef(" Status" )))
2255+ .build()
2256+
2257+ and : " an applied directive on an interface field argument with unresolved type ref"
2258+ def appliedMeta = newAppliedDirective()
2259+ .name(" meta" )
2260+ .argument(newAppliedArgument()
2261+ .name(" status" )
2262+ .type(typeRef(" Status" ))
2263+ .valueLiteral(new EnumValue (" ACTIVE" ))
2264+ .build())
2265+ .build()
2266+
2267+ and : " an interface type with a field argument that has the applied directive"
2268+ def nodeInterface = GraphQLInterfaceType . newInterface()
2269+ .name(" Node" )
2270+ .typeResolver { null }
2271+ .field(newFieldDefinition()
2272+ .name(" find" )
2273+ .type(GraphQLString )
2274+ .argument(newArgument()
2275+ .name(" filter" )
2276+ .type(GraphQLString )
2277+ .withAppliedDirective(appliedMeta)))
2278+ .build()
2279+
2280+ and : " a query type referencing the interface"
2281+ def queryType = newObject()
2282+ .name(" Query" )
2283+ .field(newFieldDefinition()
2284+ .name(" node" )
2285+ .type(typeRef(" Node" )))
2286+ .build()
2287+
2288+ when : " building with FastBuilder and validation enabled"
2289+ def schema = new GraphQLSchema.FastBuilder (
2290+ GraphQLCodeRegistry . newCodeRegistry(), queryType, null , null )
2291+ .addType(statusEnum)
2292+ .addType(nodeInterface)
2293+ .additionalDirective(metaDirective)
2294+ .withValidation(true )
2295+ .build()
2296+
2297+ then : " schema builds successfully and the applied directive arg type is resolved"
2298+ schema != null
2299+ def iface = schema. getType(" Node" ) as GraphQLInterfaceType
2300+ def fieldArg = iface. getFieldDefinition(" find" ). getArgument(" filter" )
2301+ def resolvedApplied = fieldArg. getAppliedDirective(" meta" )
2302+ resolvedApplied != null
2303+ ! (resolvedApplied. getArgument(" status" ). type instanceof GraphQLTypeReference )
2304+ resolvedApplied. getArgument(" status" ). type == statusEnum
2305+ }
2306+
2307+ def " applied directive on enum value with type-ref enum arg resolves correctly" () {
2308+ given : " a Color enum type used as the directive argument type"
2309+ def colorEnum = newEnum()
2310+ .name(" Color" )
2311+ .value(" RED" )
2312+ .value(" GREEN" )
2313+ .build()
2314+
2315+ and : " a directive definition referencing Color"
2316+ def metaDirective = newDirective()
2317+ .name(" meta" )
2318+ .validLocation(Introspection.DirectiveLocation . ENUM_VALUE )
2319+ .argument(newArgument()
2320+ .name(" color" )
2321+ .type(typeRef(" Color" )))
2322+ .build()
2323+
2324+ and : " an applied directive on an enum value with unresolved type ref"
2325+ def appliedMeta = newAppliedDirective()
2326+ .name(" meta" )
2327+ .argument(newAppliedArgument()
2328+ .name(" color" )
2329+ .type(typeRef(" Color" ))
2330+ .valueLiteral(new EnumValue (" GREEN" ))
2331+ .build())
2332+ .build()
2333+
2334+ and : " an enum type whose values have the applied directive"
2335+ def statusEnum = newEnum()
2336+ .name(" Status" )
2337+ .value(newEnumValueDefinition()
2338+ .name(" ACTIVE" )
2339+ .value(" ACTIVE" )
2340+ .withAppliedDirective(appliedMeta)
2341+ .build())
2342+ .value(" INACTIVE" )
2343+ .build()
2344+
2345+ and : " a query type"
2346+ def queryType = newObject()
2347+ .name(" Query" )
2348+ .field(newFieldDefinition()
2349+ .name(" field" )
2350+ .type(GraphQLString ))
2351+ .build()
2352+
2353+ when : " building with FastBuilder and validation enabled"
2354+ def schema = new GraphQLSchema.FastBuilder (
2355+ GraphQLCodeRegistry . newCodeRegistry(), queryType, null , null )
2356+ .addType(statusEnum)
2357+ .addType(colorEnum)
2358+ .additionalDirective(metaDirective)
2359+ .withValidation(true )
2360+ .build()
2361+
2362+ then : " schema builds successfully and the applied directive arg type is resolved"
2363+ schema != null
2364+ def resolvedStatus = schema. getType(" Status" ) as GraphQLEnumType
2365+ def activeValue = resolvedStatus. getValue(" ACTIVE" )
2366+ def resolvedApplied = activeValue. getAppliedDirective(" meta" )
2367+ resolvedApplied != null
2368+ ! (resolvedApplied. getArgument(" color" ). type instanceof GraphQLTypeReference )
2369+ resolvedApplied. getArgument(" color" ). type == colorEnum
2370+ }
21112371}
0 commit comments