E12064: both constraint references extend the same base type
What went wrong
A 'constrain by X and Y' clause has two references that extend the same base type. When two constraint references are provided, one must extend SecurityGate and the other must extend CORSPolicy. Having two SecurityGate implementations or two CORSPolicy implementations in the same constraint clause is not permitted.
Worked examples
The [EXAMPLE] section below shows a service constrained by two SecurityGates (the router expects at most one SecurityGate and one CORSPolicy), then by one gate, and by one gate and one CORS policy (good_cors.ek9).
Rationale
The constraint clause supports exactly two roles: authentication (SecurityGate) and cross-origin policy (CORSPolicy). Providing two implementations of the same role creates ambiguity about which one the router should invoke. Rather than defining precedence rules, EK9 requires one of each.
How to fix it
Ensure 'constrain by X and Y' has one SecurityGate and one CORSPolicy: 'constrain by JwtGate and ApiKeyGate', where both are security gates, fails; 'constrain by JwtGate and AllowedOrigins' (a gate and a CORS policy) is fine. If only authentication is needed (no CORS), use a single constraint: 'constrain by JwtGate'.
See also
- Security posture required: E12050
- Constraint type not resolved: E12060
- Constraint type validation: E12062
- Compilable examples: ek9 -q 1295 (Why does constrain by X and Y fail when both extend Sec...) ek9 -q 1294 (What types are valid in a constrain by clause?) ek9 -q 1292 (How does service security posture work with 'open' and ...) ek9 -q 1293 (What happens when a constrain by reference cannot be re...)
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E12064: both constraint references extend the same base type
#!ek9 defines module error.e12064.bad defines function GateAlpha is SecurityGate -> context as HTTPContext <- rtn as HTTPContext: context GateBeta is SecurityGate -> context as HTTPContext <- rtn as HTTPContext: context defines service DuplicateRoleService for :/api constrain by GateAlpha and GateBeta health() as GET for :/status <- response as HTTPResponse: () with trait of HTTPResponse override status() as pure <- rtn as Integer: 200 override content() <- rtn as Bytes: Bytes().withUtf8("ok") override contentType() as pure <- rtn as String: "text/plain" default operator ?
Fixed: One SecurityGate (and at most one CORSPolicy) per service
#!ek9 defines module error.e12064.good defines function ApiGate is SecurityGate -> context as HTTPContext <- rtn as HTTPContext: context defines service GatedService for :/api constrain by ApiGate health() as GET for :/status <- response as HTTPResponse: () with trait of HTTPResponse override status() as pure <- rtn as Integer: 200 override content() <- rtn as Bytes: Bytes().withUtf8("ok") override contentType() as pure <- rtn as String: "text/plain" default operator ?
Fixed: A SecurityGate and a CORSPolicy together are two roles, not a duplicate
#!ek9 defines module error.e12064.good.cors defines function ApiGate is SecurityGate -> context as HTTPContext <- rtn as HTTPContext: context AllowedOrigins is CORSPolicy -> origin as String <- rtn as Boolean: origin == "https://ek9.io" defines service GatedService for :/api constrain by ApiGate and AllowedOrigins health() as GET for :/status <- response as HTTPResponse: () with trait of HTTPResponse override status() as pure <- rtn as Integer: 200 override content() <- rtn as Bytes: Bytes().withUtf8("ok") override contentType() as pure <- rtn as String: "text/plain" default operator ?