diff --git a/CHANGELOG.md b/CHANGELOG.md index 98871c886a..7c44cfab32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -126,6 +126,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - [#7471](https://github.com/apache/trafficcontrol/pull/7471) *Traffic Control Cache Config (t3c)* Fixed issue with MSO non topo origins from multiple cache groups. - [#4393](https://github.com/apache/trafficcontrol/issues/4393) *Traffic Ops* Fixed the error code and alert structure when TO is queried for a delivery service with no ssl keys. - [#7590](https://github.com/apache/trafficcontrol/issues/7590) *Traffic Control Cache Config (t3c)* Fixed issue with git detected dubious ownership in repository. +- [#7575](https://github.com/apache/trafficcontrol/pull/7575) *Traffic Ops* Fixes `types` v5 apis to respond with `RFC3339` date/time Format. ### Removed - [#7271](https://github.com/apache/trafficcontrol/pull/7271) Remove components in `infrastructre/docker/`, not in use as cdn-in-a-box performs the same functionality. diff --git a/docs/source/api/v5/types.rst b/docs/source/api/v5/types.rst index d45201d789..d00066ce4a 100644 --- a/docs/source/api/v5/types.rst +++ b/docs/source/api/v5/types.rst @@ -55,7 +55,7 @@ Response Structure ------------------ :description: A short description of this type :id: An integral, unique identifier for this type -:lastUpdated: The date and time at which this type was last updated, in :ref:`non-rfc-datetime` +:lastUpdated: The date and time at which this type was last updated, in :rfc:`3339` :name: The name of this type :useInTable: The name of the Traffic Ops database table that contains objects which are grouped, identified, or described by this type @@ -77,7 +77,7 @@ Response Structure { "response": [ { "id": 48, - "lastUpdated": "2018-12-12 16:26:41+00", + "lastUpdated": "2018-12-12T10:59:07.962423+05:30", "name": "TC_LOC", "description": "Location for Traffic Control Component Servers", "useInTable": "cachegroup" @@ -124,7 +124,7 @@ Response Structure :description: A short description of this type :id: An integral, unique identifier for this type -:lastUpdated: The date and time at which this type was last updated, in :ref:`non-rfc-datetime` +:lastUpdated: The date and time at which this type was last updated, in :rfc:`3339` :name: The name of this type :useInTable: The name of the Traffic Ops database table that contains objects which are grouped, identified, or described by this type @@ -152,7 +152,7 @@ Response Structure "response": [ { "id": 3004, - "lastUpdated": "2020-02-26 18:58:41+00", + "lastUpdated": "2020-02-26T10:59:07.962423+05:30", "name": "Example01", "description": "Example" "useInTable": "server" diff --git a/docs/source/api/v5/types_id.rst b/docs/source/api/v5/types_id.rst index 598476d81c..1581080533 100644 --- a/docs/source/api/v5/types_id.rst +++ b/docs/source/api/v5/types_id.rst @@ -93,7 +93,7 @@ Response Structure "response": [ { "id": 3004, - "lastUpdated": "2020-02-26 18:58:41+00", + "lastUpdated": "2020-02-26T10:59:07.962423+05:30", "name": "Example02", "description": "Example" "useInTable": "server" diff --git a/lib/go-tc/types.go b/lib/go-tc/types.go index f6e286c0c9..3d189aa6fe 100644 --- a/lib/go-tc/types.go +++ b/lib/go-tc/types.go @@ -22,6 +22,7 @@ package tc import ( "database/sql" "errors" + "time" ) // TypesResponse is the type of a response from Traffic Ops to a GET request @@ -49,6 +50,28 @@ type TypeNullable struct { UseInTable *string `json:"useInTable" db:"use_in_table"` } +// TypesResponseV5 is type struct response used for the latest minor version associated with api major version 5. +type TypesResponseV5 = TypesResponseV50 + +// TypesResponseV50 is the type of response (for RFC3339) from Traffic Ops to a GET Request +// made to its /types API endpoint. +type TypesResponseV50 struct { + Response []TypeV50 `json:"response"` + Alerts +} + +// TypeV5 contains information about a given Type in Traffic Ops used for the latest minor version associated with api major version 5. +type TypeV5 = TypeV50 + +// TypeV50 contains information about a given Type in Traffic Ops. +type TypeV50 struct { + ID int `json:"id"` + LastUpdated time.Time `json:"lastUpdated"` + Name string `json:"name"` + Description string `json:"description"` + UseInTable string `json:"useInTable"` +} + // GetTypeData returns the type's name and use_in_table, true/false if the // query returned data, and any error. // diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go b/traffic_ops/testing/api/v5/traffic_control_test.go index 409da97887..4279332f36 100644 --- a/traffic_ops/testing/api/v5/traffic_control_test.go +++ b/traffic_ops/testing/api/v5/traffic_control_test.go @@ -55,7 +55,7 @@ type TrafficControl struct { Tenants []tc.Tenant `json:"tenants"` ServerCheckExtensions []tc.ServerCheckExtensionNullable `json:"servercheck_extensions"` Topologies []tc.Topology `json:"topologies"` - Types []tc.Type `json:"types"` + Types []tc.TypeV5 `json:"types"` SteeringTargets []tc.SteeringTargetNullable `json:"steeringTargets"` Serverchecks []tc.ServercheckRequestNullable `json:"serverchecks"` Users []tc.UserV4 `json:"users"` diff --git a/traffic_ops/testing/api/v5/types_test.go b/traffic_ops/testing/api/v5/types_test.go index e23166b56d..307744eee0 100644 --- a/traffic_ops/testing/api/v5/types_test.go +++ b/traffic_ops/testing/api/v5/types_test.go @@ -38,7 +38,7 @@ func TestTypes(t *testing.T) { currentTimeRFC := currentTime.Format(time.RFC1123) tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) - methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.Type]{ + methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.TypeV5]{ "GET": { "NOT MODIFIED when NO CHANGES made": { ClientSession: TOSession, @@ -65,7 +65,7 @@ func TestTypes(t *testing.T) { "POST": { "BAD REQUEST when INVALID useInTable NOT server": { ClientSession: TOSession, - RequestBody: tc.Type{ + RequestBody: tc.TypeV5{ Description: "Host header regular expression-Test", Name: "TEST_1", UseInTable: "regex", @@ -74,12 +74,12 @@ func TestTypes(t *testing.T) { }, "OK when VALID request when useInTable=server": { ClientSession: TOSession, - RequestBody: tc.Type{ + RequestBody: tc.TypeV5{ Description: "Host header regular expression-Test", Name: "TEST_4", UseInTable: "server", }, - Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated), validateTypeUpdateCreateFields("TEST_4", map[string]interface{}{"Name": "TEST_4"})), }, }, @@ -87,7 +87,7 @@ func TestTypes(t *testing.T) { "BAD REQUEST when useInTable NOT server": { EndpointID: GetTypeID(t, "ACTIVE_DIRECTORY"), ClientSession: TOSession, - RequestBody: tc.Type{ + RequestBody: tc.TypeV5{ Description: "Active Directory User", Name: "TEST_3", UseInTable: "cachegroup", @@ -97,7 +97,7 @@ func TestTypes(t *testing.T) { "OK when VALID request when useInTable=server": { EndpointID: GetTypeID(t, "RIAK"), ClientSession: TOSession, - RequestBody: tc.Type{ + RequestBody: tc.TypeV5{ Description: "riak type", Name: "TEST_5", UseInTable: "server", @@ -158,7 +158,7 @@ func validateTypeSort() utils.CkReqFunc { return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { assert.RequireNotNil(t, resp, "Expected Type response to not be nil.") var typeNames []string - typeResp := resp.([]tc.Type) + typeResp := resp.([]tc.TypeV5) for _, typ := range typeResp { typeNames = append(typeNames, typ.Name) } @@ -169,7 +169,7 @@ func validateTypeSort() utils.CkReqFunc { func validateTypeFields(expectedResp map[string]interface{}) utils.CkReqFunc { return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { assert.RequireNotNil(t, resp, "Expected Type response to not be nil.") - typeResp := resp.([]tc.Type) + typeResp := resp.([]tc.TypeV5) for field, expected := range expectedResp { for _, typ := range typeResp { switch field { diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index fd92566d82..396db1766c 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -345,10 +345,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `system/info/?$`, Handler: systeminfo.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 42104747531}, //Type: CRUD - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `types/?$`, Handler: api.ReadHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 422670182331}, - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `types/{id}$`, Handler: api.UpdateHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:UPDATE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4886011531}, - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `types/?$`, Handler: api.CreateHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:CREATE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 451330819531}, - {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `types/{id}$`, Handler: api.DeleteHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:DELETE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4317577331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `types/?$`, Handler: types.Read, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 422670182331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `types/{id}$`, Handler: types.Update, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:UPDATE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4886011531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `types/?$`, Handler: types.Create, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:CREATE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 451330819531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `types/{id}$`, Handler: types.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:DELETE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4317577331}, //About {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `about/?$`, Handler: about.Handler(), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 431750116631}, diff --git a/traffic_ops/traffic_ops_golang/types/types.go b/traffic_ops/traffic_ops_golang/types/types.go index 858715ebc8..ed77f4b941 100644 --- a/traffic_ops/traffic_ops_golang/types/types.go +++ b/traffic_ops/traffic_ops_golang/types/types.go @@ -21,17 +21,20 @@ package types import ( "database/sql" + "encoding/json" "errors" "fmt" "net/http" "strconv" "time" + "github.com/apache/trafficcontrol/lib/go-log" "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/lib/go-tc/tovalidate" "github.com/apache/trafficcontrol/lib/go-util" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims" validation "github.com/go-ozzo/ozzo-validation" ) @@ -211,3 +214,238 @@ func deleteQuery() string { WHERE id=:id` return query } + +// Read [V5] - gets a list of types for APIv5 +func Read(w http.ResponseWriter, r *http.Request) { + var runSecond bool + var maxTime time.Time + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + tx := inf.Tx + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + // Query Parameters to Database Query column mappings + queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{ + "name": {Column: "typ.name"}, + "id": {Column: "typ.id", Checker: api.IsInt}, + "useInTable": {Column: "typ.use_in_table"}, + } + if _, ok := inf.Params["orderby"]; !ok { + inf.Params["orderby"] = "name" + } + where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols) + if len(errs) > 0 { + api.HandleErr(w, r, tx.Tx, http.StatusBadRequest, util.JoinErrs(errs), nil) + } + + if inf.Config.UseIMS { + runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, r.Header, queryValues, SelectMaxLastUpdatedQuery(where)) + if !runSecond { + log.Debugln("IMS HIT") + api.AddLastModifiedHdr(w, maxTime) + w.WriteHeader(http.StatusNotModified) + return + } + log.Debugln("IMS MISS") + } else { + log.Debugln("Non IMS request") + } + + query := selectQuery() + where + orderBy + pagination + rows, err := tx.NamedQuery(query, queryValues) + if err != nil { + api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("type get: error getting type(s): %w", err)) + } + defer log.Close(rows, "unable to close DB connection") + + typ := tc.TypeV5{} + typeList := []tc.TypeV5{} + for rows.Next() { + if err = rows.Scan(&typ.ID, &typ.Name, &typ.Description, &typ.UseInTable, &typ.LastUpdated); err != nil { + api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("error getting type(s): %w", err)) + } + typeList = append(typeList, typ) + } + + api.WriteResp(w, r, typeList) + return +} + +// Create [V5] - creates the type with the passed data fpr APIv5. +func Create(w http.ResponseWriter, r *http.Request) { + typ := tc.TypeV5{} + + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + tx := inf.Tx.Tx + + typ, readValErr := readAndValidateJsonStructV5(r) + if readValErr != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil) + return + } + + if typ.UseInTable != "server" { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("can not create type."), nil) + return + } + + // check if type already exists + var exists bool + err := tx.QueryRow(`SELECT EXISTS(SELECT * from type where name = $1)`, typ.Name).Scan(&exists) + + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("error: %w, when checking if type with name %s exists", err, typ.Name)) + return + } + if exists { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("type name '%s' already exists.", typ.Name), nil) + return + } + + // create type + query := `INSERT INTO type (name, description, use_in_table) VALUES ($1, $2, $3) RETURNING id,last_updated` + err = tx.QueryRow(query, typ.Name, typ.Description, typ.UseInTable).Scan(&typ.ID, &typ.LastUpdated) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("error: %w in creating type with name: %s", err, typ.Name), nil) + return + } + usrErr, sysErr, code := api.ParseDBError(err) + api.HandleErr(w, r, tx, code, usrErr, sysErr) + return + } + alerts := tc.CreateAlerts(tc.SuccessLevel, "type was created.") + w.Header().Set("Location", fmt.Sprintf("/api/%d.%d/type?name=%s", inf.Version.Major, inf.Version.Minor, typ.Name)) + api.WriteAlertsObj(w, r, http.StatusCreated, alerts, typ) + return +} + +// Update [V5] - updates name & description of the type passed for APIv5. +func Update(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"}) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + tx := inf.Tx.Tx + + typ, readValErr := readAndValidateJsonStructV5(r) + if readValErr != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil) + return + } + + requestedId := inf.IntParams["id"] + // check if the entity was already updated + userErr, sysErr, errCode = api.CheckIfUnModified(r.Header, inf.Tx, requestedId, "type") + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + + if typ.UseInTable != "server" { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("can not update type."), nil) + return + } + + //update type query + query := `UPDATE type typ SET name= $1, description= $2, use_in_table= $3 WHERE typ.id=$4 RETURNING typ.id, typ.last_updated` + + err := tx.QueryRow(query, typ.Name, typ.Description, typ.UseInTable, requestedId).Scan(&typ.ID, &typ.LastUpdated) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("type with id: %d not found", requestedId), nil) + return + } + usrErr, sysErr, code := api.ParseDBError(err) + api.HandleErr(w, r, tx, code, usrErr, sysErr) + return + } + alerts := tc.CreateAlerts(tc.SuccessLevel, "type was updated") + api.WriteAlertsObj(w, r, http.StatusOK, alerts, typ) + return +} + +// Delete [V5] - deletes the type passed for APIv5. +func Delete(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + tx := inf.Tx.Tx + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + id := inf.Params["id"] + // check if type already exists + var exists bool + err := tx.QueryRow(`SELECT EXISTS(SELECT * from type where id = $1)`, id).Scan(&exists) + + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + return + } + if !exists { + api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("can not delete type"), nil) + return + } + + res, err := tx.Exec("DELETE FROM type AS typ WHERE typ.id=$1", id) + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + return + } + rowsAffected, err := res.RowsAffected() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("determining rows affected for delete type: %w", err)) + return + } + if rowsAffected == 0 { + api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("no rows deleted for type")) + return + } + + alertMessage := fmt.Sprintf("type was deleted.") + alerts := tc.CreateAlerts(tc.SuccessLevel, alertMessage) + api.WriteAlerts(w, r, http.StatusOK, alerts) + return +} + +// readAndValidateJsonStructV5 [V5] - validates the JSON object passed. +func readAndValidateJsonStructV5(r *http.Request) (tc.TypeV5, error) { + var typ tc.TypeV5 + if err := json.NewDecoder(r.Body).Decode(&typ); err != nil { + userErr := fmt.Errorf("error decoding POST request body into TypeV5 struct %w", err) + return typ, userErr + } + + // validate JSON body + rule := validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist of only alphanumeric, dash, or underscore characters") + errs := tovalidate.ToErrors(validation.Errors{ + "name": validation.Validate(typ.Name, validation.Required, rule), + "description": validation.Validate(typ.Description, validation.Required), + "use_in_table": validation.Validate(typ.UseInTable, validation.Required), + }) + if len(errs) > 0 { + userErr := util.JoinErrs(errs) + return typ, userErr + } + return typ, nil +} + +// SelectMaxLastUpdatedQuery used for TryIfModifiedSinceQuery() +func SelectMaxLastUpdatedQuery(where string) string { + return `SELECT max(t) from ( + SELECT max(last_updated) as t from type typ ` + where + + ` UNION ALL + select max(last_updated) as t from last_deleted l where l.table_name='type') as res` +} diff --git a/traffic_ops/v5-client/type.go b/traffic_ops/v5-client/type.go index 521f1d8a94..6572c51a5a 100644 --- a/traffic_ops/v5-client/type.go +++ b/traffic_ops/v5-client/type.go @@ -26,14 +26,14 @@ import ( const apiTypes = "/types" // CreateType creates the given Type. There should be a very good reason for doing this. -func (to *Session) CreateType(typ tc.Type, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { +func (to *Session) CreateType(typ tc.TypeV5, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { var alerts tc.Alerts reqInf, err := to.post(apiTypes, opts, typ, &alerts) return alerts, reqInf, err } // UpdateType replaces the Type identified by 'id' with the one provided. -func (to *Session) UpdateType(id int, typ tc.Type, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { +func (to *Session) UpdateType(id int, typ tc.TypeV5, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { route := fmt.Sprintf("%s/%d", apiTypes, id) var alerts tc.Alerts reqInf, err := to.put(route, opts, typ, &alerts) @@ -44,8 +44,8 @@ func (to *Session) UpdateType(id int, typ tc.Type, opts RequestOptions) (tc.Aler // If a 'useInTable' parameter is passed, the returned Types are restricted to those with // that exact 'useInTable' property. Only exactly 1 or exactly 0 'useInTable' parameters may // be passed; passing more will result in an error being returned. -func (to *Session) GetTypes(opts RequestOptions) (tc.TypesResponse, toclientlib.ReqInf, error) { - var data tc.TypesResponse +func (to *Session) GetTypes(opts RequestOptions) (tc.TypesResponseV5, toclientlib.ReqInf, error) { + var data tc.TypesResponseV5 reqInf, err := to.get(apiTypes, opts, &data) return data, reqInf, err }