Repository: mongodb/atlas-sdk-go
Pull request #601: chore: Export SDK functions needed by Terraform UntypedAPICall func
Author: login: lantoli
Created: 2025-05-15T13:20:53Z | Merged: 2025-05-16T11:11:11Z

## Description

Export SDK functions needed by Terraform `UntypedAPICall`.   Manual changes in this PR are only in client.mustache.

A new struct `UntypedClient` is created so `APICient` namespace is not polluted. Changes are minimized so current private functions are kept unchanged.

This was discussed and prefered to have duplicated code like in https://github.com/mongodb/terraform-provider-mongodbatlas/pull/3326.

Although they are exported functions that potentially can be used by anybody, we don't expect them to be used as they're not documented, and breaking changes are acceptable in these functions.

Link to any related issue(s): CLOUDP-316444

## Type of change:

- [ ] Bug fix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] This change requires a documentation update

## Required Checklist:

- [x] I have signed the [MongoDB CLA](https://www.mongodb.com/legal/contributor-agreement)
- [x] I have added tests that prove my fix is effective or that my feature works
- [x] I have added any necessary documentation (if appropriate)
- [x] I have run `make fmt` and formatted my code

## Further comments




Diff:
diff --git a/admin/client.go b/admin/client.go
index c09944c28..444fa2ed1 100644
--- a/admin/client.go
+++ b/admin/client.go
@@ -34,8 +34,9 @@ var (
 // APIClient manages communication with the MongoDB Atlas Administration API API
 // In most cases there should be only one, shared, APIClient.
 type APIClient struct {
-	cfg    *Configuration
-	common service // Reuse a single struct instead of allocating one for each service on the heap.
+	cfg           *Configuration
+	common        service       // Reuse a single struct instead of allocating one for each service on the heap.
+	UntypedClient UntypedClient // Make API calls without using a typed model.
 
 	// API Services
 
@@ -154,6 +155,7 @@ func NewAPIClient(cfg *Configuration) *APIClient {
 	c := &APIClient{}
 	c.cfg = cfg
 	c.common.client = c
+	c.UntypedClient.client = c
 
 	// API Services
 	c.AWSClustersDNSApi = (*AWSClustersDNSApiService)(&c.common)
@@ -356,7 +358,8 @@ type formFile struct {
 // prepareRequest build the request
 func (c *APIClient) prepareRequest(
 	ctx context.Context,
-	path string, method string,
+	path string,
+	method string,
 	postBody any,
 	headerParams map[string]string,
 	queryParams url.Values,
@@ -689,3 +692,27 @@ func FormatErrorMessageWithDetails(status, path, method string, v ApiError) stri
 		method, path, status, v.GetErrorCode(),
 		v.GetDetail(), v.GetReason(), v.GetParameters(), badRequestDetailString)
 }
+
+type UntypedClient struct {
+	client *APIClient
+}
+
+func (u *UntypedClient) PrepareRequest(
+	ctx context.Context,
+	path string,
+	method string,
+	postBody any,
+	headerParams map[string]string,
+	queryParams url.Values,
+	formParams url.Values,
+	formFiles []formFile) (localVarRequest *http.Request, err error) {
+	return u.client.prepareRequest(ctx, path, method, postBody, headerParams, queryParams, formParams, formFiles)
+}
+
+func (u *UntypedClient) CallAPI(request *http.Request) (*http.Response, error) {
+	return u.client.callAPI(request)
+}
+
+func (u *UntypedClient) MakeApiError(res *http.Response, httpMethod, httpPath string) error {
+	return u.client.makeApiError(res, httpMethod, httpPath)
+}
diff --git a/tools/config/go-templates/client.mustache b/tools/config/go-templates/client.mustache
index 384a8a0a9..9ba2d32bd 100644
--- a/tools/config/go-templates/client.mustache
+++ b/tools/config/go-templates/client.mustache
@@ -40,6 +40,7 @@ var (
 type APIClient struct {
 	cfg    *Configuration
 	common service // Reuse a single struct instead of allocating one for each service on the heap.
+	UntypedClient UntypedClient // Make API calls without using a typed model.
 
 	// API Services
 {{#apiInfo}}
@@ -66,6 +67,7 @@ func NewAPIClient(cfg *Configuration) *APIClient {
 	c := &APIClient{}
 	c.cfg = cfg
 	c.common.client = c
+	c.UntypedClient.client = c
 
 {{#apiInfo}}
 	// API Services
@@ -227,7 +229,8 @@ type formFile struct {
 // prepareRequest build the request
 func (c *APIClient) prepareRequest(
 	ctx context.Context,
-	path string, method string,
+	path string, 
+	method string,
 	postBody any,
 	headerParams map[string]string,
 	queryParams url.Values,
@@ -579,3 +582,27 @@ func FormatErrorMessageWithDetails(status, path, method string, v ApiError) stri
 		method, path, status, v.GetErrorCode(),
 		v.GetDetail(), v.GetReason(), v.GetParameters(), badRequestDetailString)
 }
+
+type UntypedClient struct {
+	client *APIClient
+}
+
+func (u *UntypedClient) PrepareRequest(
+	ctx context.Context,
+	path string, 
+	method string,
+	postBody any,
+	headerParams map[string]string,
+	queryParams url.Values,
+	formParams url.Values,
+	formFiles []formFile) (localVarRequest *http.Request, err error) {
+		return u.client.prepareRequest(ctx, path, method, postBody, headerParams, queryParams, formParams, formFiles)
+}
+
+func (u *UntypedClient) CallAPI(request *http.Request) (*http.Response, error) {
+	return u.client.callAPI(request)
+}
+
+func (u *UntypedClient) MakeApiError(res *http.Response, httpMethod, httpPath string) error {
+	return u.client.makeApiError(res, httpMethod, httpPath)
+}


Reviews:
user: login: lantoli
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-15T14:00:25Z
user: login: AgustinBettati
body: LGTM
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-16T10:00:38Z
user: login: wtrocki
state: APPROVED
author_association: MEMBER
submitted_at: 2025-05-16T10:17:37Z
user: login: lantoli
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T11:07:07Z
user: login: lantoli
state: COMMENTED
author_association: MEMBER
submitted_at: 2025-05-16T11:08:23Z

Review comments:
diff_hunk: @@ -277,18 +277,18 @@ func (a *{{{classname}}}Service) {{{nickname}}}Execute(r {{operationId}}ApiReque
 	// body params
 	localVarPostBody = r.{{paramName}}
 {{/bodyParams}}
-	req, err := a.client.prepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
+	req, err := a.client.PrepareRequest(r.ctx, localVarPath, localVarHTTPMethod, localVarPostBody, localVarHeaderParams, localVarQueryParams, localVarFormParams, formFiles)
path: tools/config/go-templates/api.mustache
user: login: lantoli
body: manual changes are only in api.mustache and client.mustache
created_at: 2025-05-15T14:00:25Z
author_association: MEMBER
diff_hunk: @@ -34,8 +34,9 @@ var (
 // APIClient manages communication with the MongoDB Atlas Administration API API
 // In most cases there should be only one, shared, APIClient.
 type APIClient struct {
-	cfg    *Configuration
-	common service // Reuse a single struct instead of allocating one for each service on the heap.
+	cfg           *Configuration
+	common        service       // Reuse a single struct instead of allocating one for each service on the heap.
+	UntypedClient UntypedClient // Make callls withouth a model.
path: admin/client.go
user: login: AgustinBettati
body: ```suggestion
	UntypedClient UntypedClient // Make API calls without using typed model
```
created_at: 2025-05-16T09:58:13Z
author_association: MEMBER
diff_hunk: @@ -689,3 +692,27 @@ func FormatErrorMessageWithDetails(status, path, method string, v ApiError) stri
 		method, path, status, v.GetErrorCode(),
 		v.GetDetail(), v.GetReason(), v.GetParameters(), badRequestDetailString)
 }
+
+type UntypedClient struct {
+	client *APIClient
+}
+
+func (u *UntypedClient) PrepareRequest(
+	ctx context.Context,
+	path string,
+	method string,
+	postBody any,
+	headerParams map[string]string,
+	queryParams url.Values,
+	formParams url.Values,
+	formFiles []formFile) (localVarRequest *http.Request, err error) {
+	return u.client.prepareRequest(ctx, path, method, postBody, headerParams, queryParams, formParams, formFiles)
+}
+
+func (u *UntypedClient) CallAPI(request *http.Request) (*http.Response, error) {
+	return u.client.callAPI(request)
+}
+
+func (u *UntypedClient) MakeApiError(res *http.Response, httpMethod, httpPath string) error {
path: admin/client.go
user: login: AgustinBettati
body: nit: Noticed this function is not using any client related info so it could be moved to a standalone function. Nevertheless better we expose it exclusively through `client.UntypedClient` to reduce exposure for now.
created_at: 2025-05-16T10:00:27Z
author_association: MEMBER
diff_hunk: @@ -34,8 +34,9 @@ var (
 // APIClient manages communication with the MongoDB Atlas Administration API API
 // In most cases there should be only one, shared, APIClient.
 type APIClient struct {
-	cfg    *Configuration
-	common service // Reuse a single struct instead of allocating one for each service on the heap.
+	cfg           *Configuration
+	common        service       // Reuse a single struct instead of allocating one for each service on the heap.
+	UntypedClient UntypedClient // Make callls withouth a model.
path: admin/client.go
user: login: lantoli
body: thanks, changed here: https://github.com/mongodb/atlas-sdk-go/pull/601/commits/2103eb4af8c445f1fd2468df58d4a73d718b48bb
created_at: 2025-05-16T11:07:07Z
author_association: MEMBER
diff_hunk: @@ -689,3 +692,27 @@ func FormatErrorMessageWithDetails(status, path, method string, v ApiError) stri
 		method, path, status, v.GetErrorCode(),
 		v.GetDetail(), v.GetReason(), v.GetParameters(), badRequestDetailString)
 }
+
+type UntypedClient struct {
+	client *APIClient
+}
+
+func (u *UntypedClient) PrepareRequest(
+	ctx context.Context,
+	path string,
+	method string,
+	postBody any,
+	headerParams map[string]string,
+	queryParams url.Values,
+	formParams url.Values,
+	formFiles []formFile) (localVarRequest *http.Request, err error) {
+	return u.client.prepareRequest(ctx, path, method, postBody, headerParams, queryParams, formParams, formFiles)
+}
+
+func (u *UntypedClient) CallAPI(request *http.Request) (*http.Response, error) {
+	return u.client.callAPI(request)
+}
+
+func (u *UntypedClient) MakeApiError(res *http.Response, httpMethod, httpPath string) error {
path: admin/client.go
user: login: lantoli
body: yes I noticed that, but I think it's better as it is now so it's "namespaced" so it's useful as a grouping mechanism
created_at: 2025-05-16T11:08:23Z
author_association: MEMBER

PR comments:
user: login: wtrocki
created_at: 2025-05-15T14:08:02Z
body: @lantoli I think that we should add namespace and avoid using public methods.

think of `client.GenericClient.methods` that will ensure you not poluting user space and make changes intentional
author_association: MEMBER
user: login: lantoli
created_at: 2025-05-16T09:52:16Z
body: @wtrocki that's a good idea, changed here: https://github.com/mongodb/atlas-sdk-go/pull/601/commits/ee482cc03d26d00bf6f1b4691c38222525cafe7f
author_association: MEMBER