8.x Add Class-typed getAttribute reads for session, request and servletContext#15732
8.x Add Class-typed getAttribute reads for session, request and servletContext#15732codeconsole wants to merge 3 commits into
Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## 8.0.x #15732 +/- ##
==================================================
+ Coverage 48.3255% 48.4425% +0.1169%
- Complexity 15216 15284 +68
==================================================
Files 1875 1875
Lines 85818 85873 +55
Branches 14969 14978 +9
==================================================
+ Hits 41472 41599 +127
+ Misses 37975 37863 -112
- Partials 6371 6411 +40
🚀 New features to boost your workflow:
|
a5f6083 to
b1fc911
Compare
✅ All tests passed ✅🏷️ Commit: b1fc911 Learn more about TestLens at testlens.app. |
| } | ||
| Object value = request.getAttribute(name) | ||
| Class<T> resolvedType = (Class<T>) ClassUtils.resolvePrimitiveIfNecessary(type) | ||
| resolvedType.isInstance(value) ? resolvedType.cast(value) : null |
There was a problem hiding this comment.
Shouldn't this be value?.class?.isAssignableFrom ?
There was a problem hiding this comment.
type.isInstance(value) is true when value is-a type, including when type is a supertype/interface of the value's concrete class. That's the primary use case for these reads:
// stored value is a concrete DefaultCsrfToken; caller requests the interface
CsrfToken token = request.getAttribute('_csrf', CsrfToken)value.class.isAssignableFrom(type) inverts the relationship — it asks "is type the same as or a subclass of the value's class", i.e. DefaultCsrfToken.isAssignableFrom(CsrfToken), which is false — so it would return null for every request-by-interface. The supertype-match case is covered by the spec (getAttribute('principal', CharSequence) for a StringBuilder value).
The isAssignableFrom equivalent of the current check would be type.isAssignableFrom(value.class) (with a null guard); type.isInstance(value) just expresses that directly and is null-safe.
|
Using grails-web-core/src/main/groovy/org/grails/web/servlet/HttpServletRequestExtension.groovy |
Summary
Follow-up to #15715. The named converters (
string,int,boolean, …) cover scalar attribute values, but attributes frequently hold richer objects — a security principal, a domain instance, a CSRF token — where today the only statically-compilable read is a cast at every call site:This adds a
Class-typed overload ofgetAttribute(plus a default-value variant) tosession,requestandservletContext:The generic signature (
static <T> T getAttribute(holder, String name, Class<T> type)) lets the static compiler infer the return type from the class literal, so the read compiles under@CompileStatic/@GrailsCompileStaticwith no cast.Design
null(so the default-value overload also applies on a type mismatch). NoTypeConvertersdelegation — the named converters remain the coercing API, theClassoverload is the type-safe retrieval API. Two simple contracts instead of one mixed one.getAttributerather than introducing a new verb. Mirrorsgrails.config.Config.getProperty(String key, Class<T> targetType, T defaultValue)(and Spring'sEnvironment.getProperty(key, type, default)), and shows up in IDE completion right next to the rawgetAttribute(String).flashis skipped — it is aMap, and Groovy's DGMMap.get(key, default)(which inserts the default) makes a two-arg sibling on flash a semantic trap.paramsis skipped — its values are Strings, where a non-coercing typed read is not useful.Tests & docs
HttpSessionExtensionSpec,HttpServletRequestExtensionSpec,ServletContextExtensionSpeceach gain two features: instance-match / supertype-match / absent / wrong-type-reads-as-null semantics, default-value behavior, and a@CompileStaticstatic-resolution guard (the guard fails test compilation without the new extension methods).:grails-web-core:testpasses in full.