返回具有数据成员的多态类型

I'm trying to write a function getTargetServer() to return a polymorphic type that has both a data member URL and a method Close(). This would be a generalization of the *Server returned from httptest.NewServer() but I want to alternatively be able to return a custom type for which Close() is a NOP.

type externalTestServer struct {
    URL string
}

func (externalTestServer) Close() {}

func getTargetServer() *externalTestServer {
    if urlbase, ok := optionals["urlbase"].(string); ok {
        return &externalTestServer{URL: urlbase}
    } else {
        testServer := httptest.NewServer(newMyServerHandler())
        // return testServer // ### Error ###
        return &externalTestServer{URL: testServer.URL}
    }
}

func Test_health_check(t *testing.T) {
    testServer := getTargetServer()
    defer testServer.Close()
    response, err := http.Get(testServer.URL + "/health")
    assert.NilError(t, err)
    assert.Assert(t, cmp.Equal(response.StatusCode, http.StatusOK))
}

This works like a charm except that Close() is always a NOP. When I uncomment the indicated ### Error ### line in order to return a closable *Server, I get the following error message:

cannot use testServer (type *httptest.Server) as type *externalTestServer in return argument

I understand the error, but haven't discovered a solution that lets me return a polymorphic type that generalizes *Server


Note: "A Tour of Go" defines an interface type as follows:

An interface type is defined as a set of method signatures.

Therefore, returning a simple interface will not allow for directly-accessible data members.

You could create a struct that has a field which is a string URL and a field that is a Close func. The Close func can be implemented by either externalTestServer or httptest.Server:

type server struct {
    URL   string
    Close func()
}
    if urlbase, ok := optionals["urlbase"].(string); ok {
        extServer := &externalTestServer{URL: urlbase}
        return &server{
            URL:   urlbase,
            Close: extServer.Close,
        }
    }
    testServer := httptest.NewServer(newMyServerHandler())
    return &server{
        URL:   testServer.URL,
        Close: testServer.Close,
    }

http.Server is a struct, so you cannot return a polymorphic object that generalizes that. You can do something else though:

type Server interface {
  GetURL() string
  Close() error
}

type testServer struct {
   URL string
}

func (t testServer) Close() error {}
func (t testServer) GetURL() string {return t.URL}

type httpServer struct {
    *http.Server
}

func (t httpServer) GetURL() string { return the url }

You can then return Server from your function.

Chris Drew's approach is ideal for this specific case, because it requires minimal code overhead. Put another way, it is the simplest thing that will solve today's problem. The solution uses the magic of an implicit closure (for the receiver) to reference the implementation of Close() polymorphically.

My slightly simplified version of that is here...

type genericServer struct {
    URL   string // snapshot of internal data
    Close func() // single-function polymorphism without 'interface'
}

func getTargetServer() genericServer {
    if urlbase, ok := optionals["urlbase"].(string); ok {
        return genericServer{URL: urlbase, Close: func() {}}
    }
    testServer := httptest.NewServer(newMyServerHandler())
    return genericServer{URL: testServer.URL, Close: testServer.Close}
}

By embedding an actual interface type into to return struct, this concept can be seamlessly extended to better support non-trivial polymorphic interfaces. In this case, the Polymorphism is explicit based on the internal use of an interface type. Still, the returned type is a wrapper that includes a copy of the (constant) member data, so the usage is identical -- effectively generalizing that of *Server for this use-case.

type Server interface {
    Close()
}

type nopServer struct {
}

func (nopServer) Close() {}

type genericServer struct {
    URL string // snapshot of specific data
    Server // embedded interface type for explicit polymorphism
}

func getTargetServer() genericServer {
    if urlbase, ok := optionals["urlbase"].(string); ok {
        return genericServer{URL: urlbase, Server: nopServer{}}
    }
    testServer := httptest.NewServer(newMyServerHandler())
    return genericServer{URL: testServer.URL, Server: testServer}
}

Note that the URL value is not a live member of the implementation, so these solutions, as presented, are only meaningful when the data value will not change, although perhaps that limitation could be overcome by using a pointer.