|
5 | 5 |
|
6 | 6 | package software.amazon.smithy.java.http.binding; |
7 | 7 |
|
| 8 | +import java.nio.ByteBuffer; |
8 | 9 | import java.util.ArrayList; |
9 | 10 | import java.util.HashMap; |
10 | 11 | import java.util.HashSet; |
|
14 | 15 | import software.amazon.smithy.java.core.schema.Schema; |
15 | 16 | import software.amazon.smithy.java.core.schema.SchemaExtensionKey; |
16 | 17 | import software.amazon.smithy.java.core.schema.SchemaExtensionProvider; |
| 18 | +import software.amazon.smithy.java.core.schema.SerializableStruct; |
17 | 19 | import software.amazon.smithy.java.core.schema.TraitKey; |
| 20 | +import software.amazon.smithy.java.core.serde.Codec; |
| 21 | +import software.amazon.smithy.java.core.serde.ShapeSerializer; |
18 | 22 | import software.amazon.smithy.java.core.serde.TimestampFormatter; |
19 | 23 | import software.amazon.smithy.java.http.api.HeaderName; |
20 | 24 | import software.amazon.smithy.model.shapes.ShapeType; |
@@ -246,29 +250,102 @@ PathSerializer pathSerializer(HttpTrait httpTrait, Schema inputSchema) { |
246 | 250 | boolean writeBody(boolean omitEmptyPayload) { |
247 | 251 | return hasBody || (!omitEmptyPayload && !hasPayload); |
248 | 252 | } |
| 253 | + |
| 254 | + // Lazy cache for the codec output of an empty struct (no body members + no payload + force-write-empty). |
| 255 | + // Bytes are determined by codec + schema, so caching once and duplicating views per request is safe. |
| 256 | + private volatile ByteBuffer cachedEmptyBody; |
| 257 | + |
| 258 | + ByteBuffer emptyBody(Codec codec, Schema schema) { |
| 259 | + var b = cachedEmptyBody; |
| 260 | + if (b == null) { |
| 261 | + b = codec.serialize(new EmptyStruct(schema)); |
| 262 | + cachedEmptyBody = b; |
| 263 | + } |
| 264 | + return b.duplicate(); |
| 265 | + } |
| 266 | + } |
| 267 | + |
| 268 | + /** |
| 269 | + * A no-member {@link SerializableStruct} used to seed the codec when caching the empty-body |
| 270 | + * representation for {@link RequestBinding#emptyBody} / {@link ResponseBinding#emptyBody}. |
| 271 | + */ |
| 272 | + private record EmptyStruct(Schema schema) implements SerializableStruct { |
| 273 | + @Override |
| 274 | + public void serializeMembers(ShapeSerializer serializer) { |
| 275 | + // no members |
| 276 | + } |
| 277 | + |
| 278 | + @Override |
| 279 | + public <T> T getMemberValue(Schema member) { |
| 280 | + return null; |
| 281 | + } |
249 | 282 | } |
250 | 283 |
|
251 | 284 | /** |
252 | 285 | * Pre-computed response-direction binding data for one structure / union schema. Built lazily |
253 | 286 | * by {@link StructBindings#response()} on first response-side access. |
254 | 287 | */ |
255 | | - record ResponseBinding( |
256 | | - Binding[] bindings, |
257 | | - MemberBinding[] memberBindings, |
258 | | - Map<String, Schema> scalarHeadersByName, |
259 | | - Schema[] listHeaderMembers, |
260 | | - HeaderName[] listHeaderNames, |
261 | | - Schema[] prefixHeadersMembers, |
262 | | - Schema payloadMember, |
263 | | - Schema statusMember, |
264 | | - boolean hasBody, |
265 | | - boolean hasPayload, |
266 | | - Set<String> headerWireNames, |
267 | | - int headerCount, |
268 | | - int defaultStatus) { |
| 288 | + static final class ResponseBinding { |
| 289 | + final Binding[] bindings; |
| 290 | + final MemberBinding[] memberBindings; |
| 291 | + final Map<String, Schema> scalarHeadersByName; |
| 292 | + final Schema[] listHeaderMembers; |
| 293 | + final HeaderName[] listHeaderNames; |
| 294 | + final Schema[] prefixHeadersMembers; |
| 295 | + final Schema payloadMember; |
| 296 | + final Schema statusMember; |
| 297 | + final boolean hasBody; |
| 298 | + final boolean hasPayload; |
| 299 | + final Set<String> headerWireNames; |
| 300 | + final int headerCount; |
| 301 | + // Default response status from @httpError or @error trait, or -1 when neither is present. |
| 302 | + final int defaultStatus; |
| 303 | + // Lazy cache for the codec output of an empty struct (no body members + no payload + force-write-empty). |
| 304 | + // Bytes are determined by codec + schema, so caching once and duplicating views per request is safe. |
| 305 | + private volatile ByteBuffer cachedEmptyBody; |
| 306 | + |
| 307 | + ResponseBinding( |
| 308 | + Binding[] bindings, |
| 309 | + MemberBinding[] memberBindings, |
| 310 | + Map<String, Schema> scalarHeadersByName, |
| 311 | + Schema[] listHeaderMembers, |
| 312 | + HeaderName[] listHeaderNames, |
| 313 | + Schema[] prefixHeadersMembers, |
| 314 | + Schema payloadMember, |
| 315 | + Schema statusMember, |
| 316 | + boolean hasBody, |
| 317 | + boolean hasPayload, |
| 318 | + Set<String> headerWireNames, |
| 319 | + int headerCount, |
| 320 | + int defaultStatus |
| 321 | + ) { |
| 322 | + this.bindings = bindings; |
| 323 | + this.memberBindings = memberBindings; |
| 324 | + this.scalarHeadersByName = scalarHeadersByName; |
| 325 | + this.listHeaderMembers = listHeaderMembers; |
| 326 | + this.listHeaderNames = listHeaderNames; |
| 327 | + this.prefixHeadersMembers = prefixHeadersMembers; |
| 328 | + this.payloadMember = payloadMember; |
| 329 | + this.statusMember = statusMember; |
| 330 | + this.hasBody = hasBody; |
| 331 | + this.hasPayload = hasPayload; |
| 332 | + this.headerWireNames = headerWireNames; |
| 333 | + this.headerCount = headerCount; |
| 334 | + this.defaultStatus = defaultStatus; |
| 335 | + } |
| 336 | + |
269 | 337 | boolean writeBody(boolean omitEmptyPayload) { |
270 | 338 | return hasBody || (!omitEmptyPayload && !hasPayload); |
271 | 339 | } |
| 340 | + |
| 341 | + ByteBuffer emptyBody(Codec codec, Schema schema) { |
| 342 | + var b = cachedEmptyBody; |
| 343 | + if (b == null) { |
| 344 | + b = codec.serialize(new EmptyStruct(schema)); |
| 345 | + cachedEmptyBody = b; |
| 346 | + } |
| 347 | + return b.duplicate(); |
| 348 | + } |
272 | 349 | } |
273 | 350 |
|
274 | 351 | /** |
|
0 commit comments