From 7f4db914ab4b7149de1b1fd98a4dcce59a8142c3 Mon Sep 17 00:00:00 2001 From: Norbert Hartl Date: Thu, 17 Sep 2026 23:02:40 +0200 Subject: [PATCH] Let a call carry a prepared Zinc handler HOPZincHandlerAdaptor mounts a foreign Zinc handler by naming its class and saying handlerClass new on every request. That leaves nobody a moment to configure the handler - the object is born inside execute and used in the same line. A metrics endpoint that has to be told which domains it exports, and under which prefix, has nowhere to be told. Calls already answer themselves as route handlers, which is how AGPluggableCall is hung into a route with its block already set. HOPZincHandlerCall does the same for anything that answers handleRequest:, so the route can prepare it where it is built - which is where the deployment knows what it wants. HOP-Extra had no test package; it has one now. Co-Authored-By: Claude Opus 5 (1M context) --- source/BaselineOfHOP/BaselineOfHOP.class.st | 2 ++ .../HOPTestZincHandler.class.st | 26 +++++++++++++++++ .../HOPZincHandlerCallTest.class.st | 28 +++++++++++++++++++ source/HOP-Extra-Tests/package.st | 1 + source/HOP-Extra/HOPZincHandlerCall.class.st | 23 +++++++++++++++ 5 files changed, 80 insertions(+) create mode 100644 source/HOP-Extra-Tests/HOPTestZincHandler.class.st create mode 100644 source/HOP-Extra-Tests/HOPZincHandlerCallTest.class.st create mode 100644 source/HOP-Extra-Tests/package.st create mode 100644 source/HOP-Extra/HOPZincHandlerCall.class.st diff --git a/source/BaselineOfHOP/BaselineOfHOP.class.st b/source/BaselineOfHOP/BaselineOfHOP.class.st index 66f9977..868cbae 100644 --- a/source/BaselineOfHOP/BaselineOfHOP.class.st +++ b/source/BaselineOfHOP/BaselineOfHOP.class.st @@ -20,6 +20,8 @@ BaselineOfHOP >> baseline: spec [ spec requires: #('HOP-Core') ]; package: 'HOP-Extra' with: [ spec requires: #('HOP-Core') ]; + package: 'HOP-Extra-Tests' with: [ + spec requires: #('HOP-Extra') ]; package: 'HOP-Roassal' with: [ spec requires: #('HOP-Core') ] ] ] diff --git a/source/HOP-Extra-Tests/HOPTestZincHandler.class.st b/source/HOP-Extra-Tests/HOPTestZincHandler.class.st new file mode 100644 index 0000000..c76707a --- /dev/null +++ b/source/HOP-Extra-Tests/HOPTestZincHandler.class.st @@ -0,0 +1,26 @@ +Class { + #name : 'HOPTestZincHandler', + #superclass : 'Object', + #instVars : [ + 'answer', + 'seenRequest' + ], + #category : 'HOP-Extra-Tests', + #package : 'HOP-Extra-Tests' +} + +{ #category : 'accessing' } +HOPTestZincHandler >> answer: aString [ + answer := aString +] + +{ #category : 'public' } +HOPTestZincHandler >> handleRequest: aRequest [ + seenRequest := aRequest. + ^ ZnResponse ok: (ZnEntity text: answer) +] + +{ #category : 'accessing' } +HOPTestZincHandler >> seenRequest [ + ^ seenRequest +] diff --git a/source/HOP-Extra-Tests/HOPZincHandlerCallTest.class.st b/source/HOP-Extra-Tests/HOPZincHandlerCallTest.class.st new file mode 100644 index 0000000..8dc940b --- /dev/null +++ b/source/HOP-Extra-Tests/HOPZincHandlerCallTest.class.st @@ -0,0 +1,28 @@ +Class { + #name : 'HOPZincHandlerCallTest', + #superclass : 'TestCase', + #category : 'HOP-Extra-Tests', + #package : 'HOP-Extra-Tests' +} + +{ #category : 'tests' } +HOPZincHandlerCallTest >> testTheConfiguredHandlerIsTheOneThatAnswers [ + "The point of carrying the handler rather than its class: whoever builds the route can + prepare it, and that same prepared object answers the request." + | handler call request | + handler := HOPTestZincHandler new answer: 'prepared'; yourself. + call := HOPZincHandlerCall new handler: handler; yourself. + request := ZnRequest get: '/anything'. + self + assert: (call handleRoute: (HOPRouteMatch new request: request; yourself)) entity contents + equals: 'prepared'. + self assert: handler seenRequest identicalTo: request +] + +{ #category : 'tests' } +HOPZincHandlerCallTest >> testACallAnswersItselfAsRouteHandler [ + "A prepared instance goes into the route as it is - that is what makes it configurable." + | call | + call := HOPZincHandlerCall new. + self assert: call asHOPRouteHandler identicalTo: call +] diff --git a/source/HOP-Extra-Tests/package.st b/source/HOP-Extra-Tests/package.st new file mode 100644 index 0000000..43fff7a --- /dev/null +++ b/source/HOP-Extra-Tests/package.st @@ -0,0 +1 @@ +Package { #name : 'HOP-Extra-Tests' } diff --git a/source/HOP-Extra/HOPZincHandlerCall.class.st b/source/HOP-Extra/HOPZincHandlerCall.class.st new file mode 100644 index 0000000..1690514 --- /dev/null +++ b/source/HOP-Extra/HOPZincHandlerCall.class.st @@ -0,0 +1,23 @@ +Class { + #name : 'HOPZincHandlerCall', + #superclass : 'HOPCall', + #instVars : [ + 'handler' + ], + #category : 'HOP-Extra', + #package : 'HOP-Extra' +} + +{ #category : 'execution' } +HOPZincHandlerCall >> execute [ + ^ handler handleRequest: self request +] + +{ #category : 'accessing' } +HOPZincHandlerCall >> handler: anObject [ + "Anything that answers handleRequest: with a ZnResponse. Carrying the handler itself rather + than its class is the point: it can be configured where the route is built, which is where + the deployment knows what it wants. HOPZincHandlerAdaptor cannot - it says handlerClass new + and leaves nobody a moment to say anything about it." + handler := anObject +]