Skip to content

Commit

Permalink
refactor(httpapi): API-specific descriptor initialization
Browse files Browse the repository at this point in the history
The underlying rationale is that I want to annotate a Descriptor
with the request and response type, using generics.

Such a change will allow me to automatically generate a swagger
to compare to the one used by the OONI API.

Reference issue: ooni/probe#2362
  • Loading branch information
bassosimone committed Dec 14, 2022
1 parent 4f4e56b commit 3e9dd60
Show file tree
Hide file tree
Showing 11 changed files with 210 additions and 348 deletions.
8 changes: 0 additions & 8 deletions internal/cmd/apitool/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,6 @@ func init() {
*input = `https://www.example.org`
}

func TestCheck(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
}
*mode = "check"
main()
}

func TestRaw(t *testing.T) {
if testing.Short() {
t.Skip("skip test in short mode")
Expand Down
3 changes: 2 additions & 1 deletion internal/engine/experiment/webconnectivity/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/ooni/probe-cli/v3/internal/httpapi"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/ooapi"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

Expand All @@ -24,7 +25,7 @@ func Control(
ctx context.Context, sess model.ExperimentSession,
testhelpers []model.OOAPIService, creq ControlRequest) (ControlResponse, *model.OOAPIService, error) {
seqCaller := httpapi.NewSequenceCaller(
httpapi.MustNewPOSTJSONWithJSONResponseDescriptor("/", creq).WithBodyLogging(true),
ooapi.NewDescriptorTH(&creq),
httpapi.NewEndpointList(sess.DefaultHTTPClient(), sess.Logger(), sess.UserAgent(), testhelpers...)...,
)
sess.Logger().Infof("control for %s...", creq.HTTPRequest)
Expand Down
10 changes: 2 additions & 8 deletions internal/engine/probeservices/checkin.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import (

"github.com/ooni/probe-cli/v3/internal/httpapi"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/runtimex"
"github.com/ooni/probe-cli/v3/internal/ooapi"
)

// CheckIn function is called by probes asking if there are tests to be run
Expand All @@ -15,13 +15,7 @@ import (
func (c Client) CheckIn(
ctx context.Context, config model.OOAPICheckInConfig) (*model.OOAPICheckInNettests, error) {
epnt := c.newHTTPAPIEndpoint()
desc, err := httpapi.NewPOSTJSONWithJSONResponseDescriptor("/api/v1/check-in", config)
// Implementation note: NewPOSTJSONWithJSONResponseDescriptor fails IFF the config type
// is not JSON serializable. Because it is, we do not expect any failure here.
runtimex.PanicOnError(err, "httpapi.NewPOSTJSONWithJSONResponseDescriptor failed")
// The response is potentially hundred of kilobytes, so we really want
// it to travel compressed over the network.
desc.AcceptEncodingGzip = true
desc := ooapi.NewDescriptorCheckIn(&config)
var response model.OOAPICheckInResult
if err := httpapi.CallWithJSONResponse(ctx, desc, epnt, &response); err != nil {
return nil, err
Expand Down
3 changes: 2 additions & 1 deletion internal/experiment/webconnectivity/control.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/ooni/probe-cli/v3/internal/measurexlite"
"github.com/ooni/probe-cli/v3/internal/model"
"github.com/ooni/probe-cli/v3/internal/netxlite"
"github.com/ooni/probe-cli/v3/internal/ooapi"
"github.com/ooni/probe-cli/v3/internal/runtimex"
)

Expand Down Expand Up @@ -110,7 +111,7 @@ func (c *Control) Run(parentCtx context.Context) {

// create an httpapi sequence caller
seqCaller := httpapi.NewSequenceCaller(
httpapi.MustNewPOSTJSONWithJSONResponseDescriptor("/", creq).WithBodyLogging(true),
ooapi.NewDescriptorTH(creq),
httpapi.NewEndpointList(c.Session.DefaultHTTPClient(), c.Logger, c.Session.UserAgent(), c.TestHelpers...)...,
)

Expand Down
94 changes: 2 additions & 92 deletions internal/httpapi/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,8 @@ package httpapi
//

import (
"encoding/json"
"net/http"
"net/url"
"time"

"github.com/ooni/probe-cli/v3/internal/runtimex"
)

// Descriptor contains the parameters for calling a given HTTP
Expand Down Expand Up @@ -55,98 +51,12 @@ type Descriptor struct {
URLQuery url.Values
}

// WithBodyLogging returns a SHALLOW COPY of [Descriptor] with LogBody set to value. You SHOULD
// only use this method when initializing the descriptor you want to use.
func (desc *Descriptor) WithBodyLogging(value bool) *Descriptor {
out := &Descriptor{}
*out = *desc
out.LogBody = value
return out
}

// DefaultMaxBodySize is the default value for the maximum
// body size you can fetch using the httpapi package.
const DefaultMaxBodySize = 1 << 24

// DefaultCallTimeout is the default timeout for an httpapi call.
const DefaultCallTimeout = 60 * time.Second

// NewGETJSONDescriptor is a convenience factory for creating a new descriptor
// that uses the GET method and expects a JSON response.
func NewGETJSONDescriptor(urlPath string) *Descriptor {
return NewGETJSONWithQueryDescriptor(urlPath, url.Values{})
}

// applicationJSON is the content-type for JSON
const applicationJSON = "application/json"

// NewGETJSONWithQueryDescriptor is like NewGETJSONDescriptor but it also
// allows you to provide query arguments. Leaving query nil or empty
// is equivalent to calling NewGETJSONDescriptor directly.
func NewGETJSONWithQueryDescriptor(urlPath string, query url.Values) *Descriptor {
return &Descriptor{
Accept: applicationJSON,
AcceptEncodingGzip: false,
Authorization: "",
ContentType: "",
LogBody: false,
MaxBodySize: DefaultMaxBodySize,
Method: http.MethodGet,
RequestBody: nil,
Timeout: DefaultCallTimeout,
URLPath: urlPath,
URLQuery: query,
}
}

// NewPOSTJSONWithJSONResponseDescriptor creates a descriptor that POSTs a JSON document
// and expects to receive back a JSON document from the API.
//
// This function ONLY fails if we cannot serialize the request to JSON. So, if you know
// that request is JSON-serializable, you can safely call MustNewPostJSONWithJSONResponseDescriptor instead.
func NewPOSTJSONWithJSONResponseDescriptor(urlPath string, request any) (*Descriptor, error) {
rawRequest, err := json.Marshal(request)
if err != nil {
return nil, err
}
desc := &Descriptor{
Accept: applicationJSON,
AcceptEncodingGzip: false,
Authorization: "",
ContentType: applicationJSON,
LogBody: false,
MaxBodySize: DefaultMaxBodySize,
Method: http.MethodPost,
RequestBody: rawRequest,
Timeout: DefaultCallTimeout,
URLPath: urlPath,
URLQuery: nil,
}
return desc, nil
}

// MustNewPOSTJSONWithJSONResponseDescriptor is like NewPOSTJSONWithJSONResponseDescriptor except that
// it panics in case it's not possible to JSON serialize the request.
func MustNewPOSTJSONWithJSONResponseDescriptor(urlPath string, request any) *Descriptor {
desc, err := NewPOSTJSONWithJSONResponseDescriptor(urlPath, request)
runtimex.PanicOnError(err, "NewPOSTJSONWithJSONResponseDescriptor failed")
return desc
}

// NewGETResourceDescriptor creates a generic descriptor for GETting a
// resource of unspecified type using the given urlPath.
func NewGETResourceDescriptor(urlPath string) *Descriptor {
return &Descriptor{
Accept: "",
AcceptEncodingGzip: false,
Authorization: "",
ContentType: "",
LogBody: false,
MaxBodySize: DefaultMaxBodySize,
Method: http.MethodGet,
RequestBody: nil,
Timeout: DefaultCallTimeout,
URLPath: urlPath,
URLQuery: url.Values{},
}
}
// ApplicationJSON is the content-type for JSON
const ApplicationJSON = "application/json"
Loading

0 comments on commit 3e9dd60

Please sign in to comment.