Repository: caicloud/nirvana
Pull request #451 (2021-01-21 07:52:38)

refactor(*): support add example in parameter
<!--  Thanks for sending a pull request! See below for tips! -->

**What this PR does / why we need it**:

Support add example in parameter, before:

<img width="1405" alt="Screen Shot 2021-01-21 at 15 25 45" src="https://user-images.githubusercontent.com/9134003/105319829-6178e380-5c00-11eb-96e2-b0e99606a036.png">

After:

<img width="1413" alt="Screen Shot 2021-01-21 at 15 24 49" src="https://user-images.githubusercontent.com/9134003/105319844-650c6a80-5c00-11eb-86c0-c6b5ec4b225b.png">

**Which issue(s) this PR is related to** *(optional, link to 3rd issue(s))*:

Fixes #

Reference to #
<!-- 填在 Fixes，PR 合并就会关 issue。填在 Reference to 会关联 issue，不会联动关闭。-->

**Special notes for your reviewer**:

/cc @xpofei 

<!-- Please answer the following questions during the code freeze, and delete this line.
**Code freeze questions**

1. What causes this PR to not be merged before code freeze?
2. Why this PR is absolutely necessary for this version? Paste a screenshot of smoke testing docs if you could.
3. What's the effects after merging it?
4. Is there anyway we can skip this to not affect the overall process?
-->

**Release note**:
<!--  Write your release note:
1. Enter your extended release note in the below block. If the PR requires additional action from users switching to the new release, include the string "action required".
2. If no release note is required, just write "NONE".
-->

```release-note
NONE
```

<!--  Thanks for sending a pull request! Here are some tips:

1. https://github.com/caicloud/engineering/blob/master/guidelines/review_conventions.md      <-- what is the review process looks like
2. https://github.com/caicloud/engineering/blob/master/guidelines/git_commit_conventions.md  <-- how to structure your git commit
3. https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md            <-- how to work with caicloud bot

Other tips:

If this is your first contribution, read our Getting Started guide https://github.com/caicloud/engineering/blob/master/guidelines/README.md
-->


caicloud-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: 
To fully approve this pull request, please assign additional approvers.
We suggest the following additional approver: **supereagle**

Assign the PR to them by writing `/assign @supereagle` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#bot-commands).

The pull request process is described [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#typical-workflow)

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/caicloud/nirvana/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["supereagle"]} -->
xpofei: /lgtm
caicloud-bot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/caicloud/nirvana/pull/451#issuecomment-765256608" title="LGTM">xpofei</a>*
To fully approve this pull request, please assign additional approvers.
We suggest the following additional approver: **supereagle**

Assign the PR to them by writing `/assign @supereagle` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#bot-commands).

The pull request process is described [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#typical-workflow)

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/caicloud/nirvana/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["supereagle"]} -->
iawia002: /approve
caicloud-bot: [APPROVALNOTIFIER] This PR is **APPROVED**

This pull-request has been approved by: *<a href="https://github.com/caicloud/nirvana/pull/451#issuecomment-765259830" title="Approved">iawia002</a>*, *<a href="https://github.com/caicloud/nirvana/pull/451#issuecomment-765256608" title="LGTM">xpofei</a>*

The full list of commands accepted by this bot can be found [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#bot-commands).

The pull request process is described [here](https://github.com/caicloud/engineering/blob/master/guidelines/caicloud_bot.md#typical-workflow)

<details >
Needs approval from an approver in each of these files:

- ~~[OWNERS](https://github.com/caicloud/nirvana/blob/master/OWNERS)~~ [iawia002]

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":[]} -->

Patch:
diff --git a/definition/definition.go b/definition/definition.go
index 4369a83..f807e28 100644
--- a/definition/definition.go
+++ b/definition/definition.go
@@ -148,14 +148,6 @@ const (
 	Error Destination = "Error"
 )
 
-// Example is just an example.
-type Example struct {
-	// Description describes the example.
-	Description string
-	// Instance is a custom data.
-	Instance interface{}
-}
-
 // Parameter describes a function parameter.
 type Parameter struct {
 	// Source is the parameter value generated from.
@@ -194,6 +186,9 @@ type Parameter struct {
 	//    // do something else
 	// }
 	Optional bool
+	// Example defines the example value for a parameter, used in API docs generation.
+	// Swagger 2 does not support requestBody example, this value will be mapped to the example field of the corresponding structure definition.
+	Example interface{}
 }
 
 // Result describes how to handle a result from function results.
@@ -237,6 +232,6 @@ type Definition struct {
 	Summary string
 	// Description describes the API handler.
 	Description string
-	// Examples contains many examples for the API handler.
-	Examples []Example
+	// Example contains the example for the API handler.
+	Example interface{}
 }
diff --git a/definition/rpc.go b/definition/rpc.go
index 8ae74fa..f808ace 100644
--- a/definition/rpc.go
+++ b/definition/rpc.go
@@ -69,6 +69,6 @@ type RPCAction struct {
 	Results []Result
 	// Description describes the API handler.
 	Description string
-	// Examples contains many examples for the API handler.
-	Examples []Example
+	// Example contains the example for the API handler.
+	Example interface{}
 }
diff --git a/service/rest/builder.go b/service/rest/builder.go
index 6f3e5f1..eca99a1 100644
--- a/service/rest/builder.go
+++ b/service/rest/builder.go
@@ -134,6 +134,7 @@ func (b *builder) copyDefinition(d *definition.Definition, consumes []string, pr
 		Summary:     d.Summary,
 		Function:    d.Function,
 		Description: d.Description,
+		Example:     d.Example,
 	}
 	if len(d.Consumes) > 0 {
 		consumes = d.Consumes
@@ -173,8 +174,6 @@ func (b *builder) copyDefinition(d *definition.Definition, consumes []string, pr
 		copy(newResult.Operators, r.Operators)
 		newOne.Results[i] = newResult
 	}
-	newOne.Examples = make([]definition.Example, len(d.Examples))
-	copy(newOne.Examples, d.Examples)
 	return newOne
 }
 
diff --git a/service/rpc/builder.go b/service/rpc/builder.go
index b90f60a..41b1182 100644
--- a/service/rpc/builder.go
+++ b/service/rpc/builder.go
@@ -146,7 +146,7 @@ func (b *builder) genDefinition(action definition.RPCAction, consumes []string,
 		Results:       action.Results,
 		Summary:       action.Name,
 		Description:   action.Description,
-		Examples:      action.Examples,
+		Example:       action.Example,
 	}
 }
 
diff --git a/utils/api/definitions.go b/utils/api/definitions.go
index 2c60e7b..49c01ba 100644
--- a/utils/api/definitions.go
+++ b/utils/api/definitions.go
@@ -17,7 +17,6 @@ limitations under the License.
 package api
 
 import (
-	"encoding/json"
 	"fmt"
 	"net/http"
 
@@ -35,10 +34,12 @@ type Parameter struct {
 	Description string
 	// Type is parameter object type.
 	Type TypeName
-	// Default is encoded default value.
-	Default []byte
+	// Default is the default value.
+	Default interface{}
 	// Optional used to set whether this parameter is optional or not.
 	Optional bool
+	// Example is the example value.
+	Example interface{}
 }
 
 // Result describes a function result.
@@ -51,16 +52,6 @@ type Result struct {
 	Type TypeName
 }
 
-// Example is just an example.
-type Example struct {
-	// Description describes the example.
-	Description string
-	// Type is result object type.
-	Type TypeName
-	// Instance is encoded instance data.
-	Instance []byte
-}
-
 // Definition is complete version of def.Definition.
 type Definition struct {
 	// Method is definition method.
@@ -93,8 +84,8 @@ type Definition struct {
 	Parameters []Parameter
 	// Results describes function retrun values.
 	Results []Result
-	// Examples contains many examples for the API handler.
-	Examples []Example
+	// Example is the example value.
+	Example interface{}
 }
 
 // NewDefinition creates openapi.Definition from definition.Definition.
@@ -115,6 +106,7 @@ func NewDefinition(tc *TypeContainer, d *definition.Definition, apiStyle service
 		Produces:      d.Produces,
 		ErrorProduces: d.ErrorProduces,
 		Function:      tc.NameOfInstance(d.Function),
+		Example:       d.Example,
 	}
 	if d.Method == definition.Any {
 		cd.HTTPMethod = string(definition.Any)
@@ -134,13 +126,8 @@ func NewDefinition(tc *TypeContainer, d *definition.Definition, apiStyle service
 			Description: p.Description,
 			Type:        functionType.In[i].Type,
 			Optional:    p.Optional,
-		}
-		if p.Default != nil {
-			data, err := encode(p.Default)
-			if err != nil {
-				return nil, err
-			}
-			param.Default = data
+			Default:     p.Default,
+			Example:     p.Example,
 		}
 		if len(p.Operators) > 0 {
 			param.Type = tc.NameOf(p.Operators[0].In())
@@ -158,20 +145,6 @@ func NewDefinition(tc *TypeContainer, d *definition.Definition, apiStyle service
 		}
 		cd.Results = append(cd.Results, result)
 	}
-	for _, e := range d.Examples {
-		example := Example{
-			Description: e.Description,
-		}
-		if e.Instance != nil {
-			example.Type = tc.NameOfInstance(e.Instance)
-			data, err := encode(e.Instance)
-			if err != nil {
-				return nil, err
-			}
-			example.Instance = data
-		}
-		cd.Examples = append(cd.Examples, example)
-	}
 	return cd, nil
 }
 
@@ -201,8 +174,3 @@ func NewPathDefinitions(tc *TypeContainer, definitions map[string][]definition.D
 	}
 	return result, nil
 }
-
-// encode encodes instance to json format.
-func encode(ins interface{}) ([]byte, error) {
-	return json.Marshal(ins)
-}
diff --git a/utils/generators/swagger/generator.go b/utils/generators/swagger/generator.go
index 66e992e..ddfef80 100644
--- a/utils/generators/swagger/generator.go
+++ b/utils/generators/swagger/generator.go
@@ -464,7 +464,7 @@ func (g *Generator) operationFor(def *api.Definition) *spec.Operation {
 	operation.Responses = &spec.Responses{
 		ResponsesProps: spec.ResponsesProps{
 			StatusCodeResponses: map[int]spec.Response{
-				def.HTTPCode: *g.generateResponse(def.Results, def.Examples),
+				def.HTTPCode: *g.generateResponse(def.Results, def.Example),
 			},
 		},
 	}
@@ -489,8 +489,8 @@ func (g *Generator) generateParameter(param *api.Parameter) []spec.Parameter {
 			Required:    !param.Optional,
 		},
 	}
-	if len(param.Default) > 0 {
-		parameter.Required = false
+	if param.Default != nil {
+		parameter.WithDefault(param.Default)
 	}
 	body := "body"
 	if parameter.In != body {
@@ -508,17 +508,16 @@ func (g *Generator) generateParameter(param *api.Parameter) []spec.Parameter {
 			parameter.Items.Format = schema.Items.Schema.Format
 		}
 		parameter.Schema = nil
+		parameter.SimpleSchema.Example = param.Example
 	} else {
 		// add parameter name for body, it required by swagger ui,
 		// cause api.Parameter.Name is always nil when In is body
 		parameter.Name = body
+		k := strings.Replace(string(param.Type), "/", "_", -1)
+		if v, ok := g.schemas[k]; ok {
+			v.WithExample(param.Example)
+		}
 	}
-
-	if len(param.Default) > 0 {
-		r := rawJSON(param.Default)
-		parameter.WithDefault(&r)
-	}
-
 	return []spec.Parameter{parameter}
 }
 
@@ -627,7 +626,7 @@ func parseDestination(d definition.Destination) definition.Destination {
 	}
 }
 
-func (g *Generator) generateResponse(results []api.Result, examples []api.Example) *spec.Response {
+func (g *Generator) generateResponse(results []api.Result, example interface{}) *spec.Response {
 	response := &spec.Response{}
 	for _, result := range results {
 		switch g.destinationMapping[parseDestination(result.Destination)] {
@@ -640,14 +639,7 @@ func (g *Generator) generateResponse(results []api.Result, examples []api.Exampl
 			response.Schema = schema
 		}
 	}
-	for _, example := range examples {
-		if len(example.Instance) > 0 {
-			// Only show the first example which has data.
-			r := rawJSON(example.Instance)
-			response.AddExample("application/json", &r)
-			break
-		}
-	}
+	response.AddExample("application/json", example)
 	if response.Schema == nil && response.Description == "" {
 		response.Description = "No Content"
 	}
@@ -657,14 +649,3 @@ func (g *Generator) generateResponse(results []api.Result, examples []api.Exampl
 func (g *Generator) escapeNewline(content string) string {
 	return strings.Replace(strings.TrimSpace(content), "\n", "<br/>", -1)
 }
-
-type rawJSON []byte
-
-func (r *rawJSON) UnmarshalJSON(data []byte) error {
-	*r = data
-	return nil
-}
-
-func (r *rawJSON) MarshalJSON() ([]byte, error) {
-	return *r, nil
-}


Test patch:
diff --git a/service/rpc/builder_test.go b/service/rpc/builder_test.go
index bc3f42c..7170073 100644
--- a/service/rpc/builder_test.go
+++ b/service/rpc/builder_test.go
@@ -252,7 +252,7 @@ func TestDefinitions(t *testing.T) {
 				Results:       desc1.Actions[0].Results,
 				Summary:       "summary",
 				Description:   "",
-				Examples:      nil,
+				Example:       nil,
 			},
 		},
 		"/?Version=2020-01-01&Action=GetEcho2": {
@@ -267,7 +267,7 @@ func TestDefinitions(t *testing.T) {
 				Results:       desc1.Actions[1].Results,
 				Summary:       "summary",
 				Description:   "",
-				Examples:      nil,
+				Example:       nil,
 			},
 		},
 		"/aaa?Version=2020-01-01&Action=GetEcho": {
@@ -282,7 +282,7 @@ func TestDefinitions(t *testing.T) {
 				Results:       desc2.Actions[0].Results,
 				Summary:       "summary",
 				Description:   "",
-				Examples:      nil,
+				Example:       nil,
 			},
 		},
 	}