Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support caller-specified RetryFuncs #4428

Merged
merged 6 commits into from
Sep 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ type Operation struct {
}

type Option struct {
// Type signals a special behavior for this Option.
// Data: this option specifies Request data, as described in ObjectDefinition, HeaderName, ODataFieldName and/or QueryStringName.
// ContentType: this option specifies a custom Content Type for the Request to be specified by the caller.
// RetryFunc: this option specifies a client.RequestRetryFunc that can be passed in.
Type string

// HeaderName is the name of the Http Header which this Option should be set into
// (e.g. `If-Match`, `x-ms-client-request-id`)
HeaderName *string `json:"headerName,omitempty"`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,32 +12,49 @@ import (
)

func mapSDKOperationOptionFromRepository(input repositoryModels.Option, knownData helpers.KnownData) (*sdkModels.SDKOperationOption, error) {
objectDefinition, err := mapSDKOperationOptionObjectDefinitionFromRepository(input.ObjectDefinition, knownData)
if err != nil {
return nil, fmt.Errorf("mapping the ObjectDefinition: %+v", err)
var objectDefinition sdkModels.SDKOperationOptionObjectDefinition

if input.Type == "Data" {
mapping, err := mapSDKOperationOptionObjectDefinitionFromRepository(input.ObjectDefinition, knownData)
if err != nil {
return nil, fmt.Errorf("mapping the ObjectDefinition: %+v", err)
}
objectDefinition = *mapping
}

return &sdkModels.SDKOperationOption{
Type: input.Type,
HeaderName: input.HeaderName,
ODataFieldName: input.ODataFieldName,
QueryStringName: input.QueryString,
ObjectDefinition: *objectDefinition,
ObjectDefinition: objectDefinition,
Required: input.Required,
}, nil
}

func mapSDKOperationOptionToRepository(fieldName string, input sdkModels.SDKOperationOption, knownData helpers.KnownData) (*repositoryModels.Option, error) {
objectDefinition, err := mapSDKOperationOptionObjectDefinitionToRepository(input.ObjectDefinition, knownData)
if err != nil {
return nil, fmt.Errorf("mapping the object definition: %+v", err)
optionType := sdkModels.SDKOperationOptionTypeData
if input.Type != "" {
optionType = input.Type
}

var objectDefinition repositoryModels.OptionObjectDefinition

if optionType == sdkModels.SDKOperationOptionTypeData {
mapping, err := mapSDKOperationOptionObjectDefinitionToRepository(input.ObjectDefinition, knownData)
if err != nil {
return nil, fmt.Errorf("mapping the object definition: %+v", err)
}
objectDefinition = *mapping
}

option := repositoryModels.Option{
Type: optionType,
HeaderName: input.HeaderName,
ODataFieldName: input.ODataFieldName,
QueryString: input.QueryStringName,
Field: fieldName,
ObjectDefinition: *objectDefinition,
ObjectDefinition: objectDefinition,
}

if !input.Required {
Expand Down
14 changes: 14 additions & 0 deletions tools/data-api-sdk/v1/models/sdk_operation_option.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,26 @@

package models

type SDKOperationOptionType = string

const (
SDKOperationOptionTypeData SDKOperationOptionType = "Data"
SDKOperationOptionTypeContentType SDKOperationOptionType = "ContentType"
SDKOperationOptionTypeRetryFunc SDKOperationOptionType = "RetryFunc"
)

// SDKOperationOption defines a QueryString or HTTP Header that can be specified for an
// Operation.
type SDKOperationOption struct {
// HeaderName specifies the name of the HTTP Header associated with this Option.
HeaderName *string `json:"headerName,omitempty"`

// Type signals a special behavior for this Option.
// Data: this option specifies Request data, as described in ObjectDefinition, HeaderName, ODataFieldName and/or QueryStringName.
// ContentType: this option specifies a custom Content Type for the Request to be specified by the caller.
// RetryFunc: this option specifies a client.RequestRetryFunc that can be passed in.
Type SDKOperationOptionType

// ODataFieldName specifies the name for the OData query string parameter associated with this Option.
ODataFieldName *string `json:"odataFieldName,omitempty"`

Expand Down
50 changes: 41 additions & 9 deletions tools/generator-go-sdk/internal/generator/templater_methods.go
Original file line number Diff line number Diff line change
Expand Up @@ -579,19 +579,36 @@ func (c methodsPandoraTemplater) requestOptions() (*string, error) {
}
}

items := []string{
fmt.Sprintf("ContentType: %q", c.operation.ContentType),
fmt.Sprintf(`ExpectedStatusCodes: []int{
%s,
}`, strings.Join(expectedStatusCodes, ",\n\t\t\t")),
fmt.Sprintf("HttpMethod: http.Method%s", method),
fmt.Sprintf("Path: %s", path),
options := map[string]string{
"ContentType": fmt.Sprintf("%q", c.operation.ContentType),
"ExpectedStatusCodes": fmt.Sprintf("[]int{\n\t\t\t%s,\n}", strings.Join(expectedStatusCodes, ",\n\t\t\t")),
"HttpMethod": fmt.Sprintf("http.Method%s", method),
"Path": path,
}

if len(c.operation.Options) > 0 {
items = append(items, "OptionsObject: options")
options["OptionsObject"] = "options"

// Look for special options
for optionName, option := range c.operation.Options {
switch option.Type {
case models.SDKOperationOptionTypeContentType:
options["ContentType"] = fmt.Sprintf("options.%s", optionName)
break
case models.SDKOperationOptionTypeRetryFunc:
options["RetryFunc"] = fmt.Sprintf("options.%s", optionName)
break
}
}
}

if c.operation.FieldContainingPaginationDetails != nil {
items = append(items, fmt.Sprintf("Pager: &%sCustomPager{}", c.operationName))
options["Pager"] = fmt.Sprintf("&%sCustomPager{}", c.operationName)
}

items := make([]string, 0, len(options))
for key, value := range options {
items = append(items, fmt.Sprintf("%s: %s", key, value))
}
sort.Strings(items)

Expand Down Expand Up @@ -830,25 +847,40 @@ func (c methodsPandoraTemplater) optionsStruct(data GeneratorData) (*string, err
headerAssignments := make([]string, 0)

for optionName, option := range c.operation.Options {
// Handle special options
switch option.Type {
case models.SDKOperationOptionTypeContentType:
properties = append(properties, fmt.Sprintf("%s string", optionName))
continue
case models.SDKOperationOptionTypeRetryFunc:
properties = append(properties, fmt.Sprintf("%s client.RequestRetryFunc", optionName))
continue
}

optionType, err := helpers.GolangTypeForSDKOperationOptionObjectDefinition(option.ObjectDefinition)
if err != nil {
return nil, fmt.Errorf("determining golang type name for option %q's ObjectDefinition: %+v", optionName, err)
}

properties = append(properties, fmt.Sprintf("%s *%s", optionName, *optionType))

if option.ODataFieldName != nil {
value := fmt.Sprintf("*o.%s", *option.ODataFieldName)
if option.ObjectDefinition.Type == models.IntegerSDKOperationOptionObjectDefinitionType {
value = fmt.Sprintf("int(%s)", value)
}

odataAssignments = append(odataAssignments, fmt.Sprintf(`if o.%[1]s != nil {
out.%[2]s = %[3]s
}`, optionName, *option.ODataFieldName, value))
}

if option.HeaderName != nil {
headerAssignments = append(headerAssignments, fmt.Sprintf(`if o.%[1]s != nil {
out.Append("%[2]s", fmt.Sprintf("%%v", *o.%[1]s))
}`, optionName, *option.HeaderName))
}

if option.QueryStringName != nil {
queryStringAssignments = append(queryStringAssignments, fmt.Sprintf(`if o.%[1]s != nil {
out.Append("%[2]s", fmt.Sprintf("%%v", *o.%[1]s))
Expand Down
197 changes: 197 additions & 0 deletions tools/generator-go-sdk/internal/generator/templater_methods_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -586,3 +586,200 @@ func (c pandaClient) ListCompleteMatchingPredicate(ctx context.Context, id Panda

assertTemplatedCodeMatches(t, expected, *actual)
}

func TestTemplateGetMethodWithRetryFuncOption(t *testing.T) {
input := GeneratorData{
baseClientPackage: "testclient",
packageName: "skinnyPandas",
serviceClientName: "pandaClient",
source: AccTestLicenceType,
resourceIds: map[string]models.ResourceID{
"PandaPop": {
ExampleValue: "LingLing",
},
},
}

actual, err := methodsPandoraTemplater{
operation: models.SDKOperation{
ContentType: "application/json",
ExpectedStatusCodes: []int{200},
Method: "GET",
Options: map[string]models.SDKOperationOption{
"TheRetryFunc": {
Type: models.SDKOperationOptionTypeRetryFunc,
},
},
ResourceIDName: stringPointer("PandaPop"),
ResponseObject: &models.SDKObjectDefinition{
Type: models.StringSDKObjectDefinitionType,
},
},
operationName: "Get",
}.immediateOperationTemplate(input)
if err != nil {
t.Fatalf("err %+v", err)
}

expected := `
type GetOperationResponse struct {
HttpResponse *http.Response
OData *odata.OData
Model *string
}

type GetOperationOptions struct {
TheRetryFunc client.RequestRetryFunc
}

func DefaultGetOperationOptions() GetOperationOptions {
return GetOperationOptions{}
}

func (o GetOperationOptions) ToHeaders() *client.Headers {
out := client.Headers{}
return &out
}

func (o GetOperationOptions) ToOData() *odata.Query {
out := odata.Query{}
return &out
}

func (o GetOperationOptions) ToQuery() *client.QueryParams {
out := client.QueryParams{}
return &out
}

// Get ...
func (c pandaClient) Get(ctx context.Context , id PandaPop, options GetOperationOptions) (result GetOperationResponse, err error) {
opts := client.RequestOptions{
ContentType: "application/json",
ExpectedStatusCodes: []int{
http.StatusOK,
},
HttpMethod: http.MethodGet,
OptionsObject: options,
Path: id.ID(),
RetryFunc: options.TheRetryFunc,
}

req, err := c.Client.NewRequest(ctx, opts)
if err != nil {
return
}

var resp *client.Response
resp, err = req.Execute(ctx)
if resp != nil {
result.OData = resp.OData
result.HttpResponse = resp.Response
}
if err != nil {
return
}

var model string
result.Model = &model
if err = resp.Unmarshal(result.Model); err != nil {
return
}

return
}
`
assertTemplatedCodeMatches(t, expected, *actual)
}

func TestTemplatePutMethodWithContentTypeOption(t *testing.T) {
input := GeneratorData{
baseClientPackage: "testclient",
packageName: "skinnyPandas",
serviceClientName: "pandaClient",
source: AccTestLicenceType,
resourceIds: map[string]models.ResourceID{
"PandaPop": {
ExampleValue: "LingLing",
},
},
}

actual, err := methodsPandoraTemplater{
operation: models.SDKOperation{
ContentType: "application/json",
ExpectedStatusCodes: []int{204},
Method: "PUT",
Options: map[string]models.SDKOperationOption{
"UploadContentType": {
Type: models.SDKOperationOptionTypeContentType,
},
},
ResourceIDName: stringPointer("PandaPop"),
},
operationName: "Put",
}.immediateOperationTemplate(input)
if err != nil {
t.Fatalf("err %+v", err)
}

expected := `
type PutOperationResponse struct {
HttpResponse *http.Response
OData *odata.OData
}

type PutOperationOptions struct {
UploadContentType string
}

func DefaultPutOperationOptions() PutOperationOptions {
return PutOperationOptions{}
}

func (o PutOperationOptions) ToHeaders() *client.Headers {
out := client.Headers{}
return &out
}

func (o PutOperationOptions) ToOData() *odata.Query {
out := odata.Query{}
return &out
}

func (o PutOperationOptions) ToQuery() *client.QueryParams {
out := client.QueryParams{}
return &out
}

// Put ...
func (c pandaClient) Put(ctx context.Context , id PandaPop, options PutOperationOptions) (result PutOperationResponse, err error) {
opts := client.RequestOptions{
ContentType: options.UploadContentType,
ExpectedStatusCodes: []int{
http.StatusNoContent,
},
HttpMethod: http.MethodPut,
OptionsObject: options,
Path: id.ID(),
}

req, err := c.Client.NewRequest(ctx, opts)
if err != nil {
return
}

var resp *client.Response
resp, err = req.Execute(ctx)
if resp != nil {
result.OData = resp.OData
result.HttpResponse = resp.Response
}
if err != nil {
return
}

return
}
`
assertTemplatedCodeMatches(t, expected, *actual)
}
Original file line number Diff line number Diff line change
Expand Up @@ -428,8 +428,9 @@ func (p pipelineForService) parseResources(resourceIds parser.ResourceIds, model
continue
}

// Binary payloads are handled by the SDK
if content.Schema.Value != nil && content.Schema.Value.Format == "binary" {
// Set the request type to Binary. When translating to Data API SDK types, we will also work in a
// ContentType option to be set by the caller.
requestType = pointer.To(parser.DataTypeBinary)
break
}
Expand Down
Loading
Loading