diff --git a/internal/apiserver/ffi2swagger.go b/internal/apiserver/ffi2swagger.go index cde43a1125..83f1d8805b 100644 --- a/internal/apiserver/ffi2swagger.go +++ b/internal/apiserver/ffi2swagger.go @@ -71,7 +71,7 @@ func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core }, } for _, method := range ffi.Methods { - routes = og.addMethod(routes, method, hasLocation) + routes = og.addMethod(ctx, routes, method, hasLocation) } for _, event := range ffi.Events { routes = og.addEvent(routes, event, hasLocation) @@ -86,8 +86,7 @@ func (og *ffiSwaggerGen) Generate(ctx context.Context, baseURL string, api *core }).Generate(ctx, routes) } -func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *fftypes.FFIMethod, hasLocation bool) []*ffapi.Route { - ctx := context.Background() +func (og *ffiSwaggerGen) addMethod(ctx context.Context, routes []*ffapi.Route, method *fftypes.FFIMethod, hasLocation bool) []*ffapi.Route { description := method.Description if method.Details != nil && len(method.Details) > 0 { additionalDetailsHeader := i18n.Expand(ctx, coremsgs.APISmartContractDetails) @@ -98,10 +97,10 @@ func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *fftypes.FFIMet Path: fmt.Sprintf("invoke/%s", method.Pathname), // must match a route defined in apiserver routes! Method: http.MethodPost, JSONInputSchema: func(ctx context.Context, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) { - return contractJSONSchema(&method.Params, hasLocation) + return contractJSONSchema(ctx, &method.Params, hasLocation) }, JSONOutputSchema: func(ctx context.Context, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) { - return contractJSONSchema(&method.Returns, true) + return contractJSONSchema(ctx, &method.Returns, true) }, JSONOutputCodes: []int{http.StatusOK}, PreTranslatedDescription: description, @@ -111,10 +110,10 @@ func (og *ffiSwaggerGen) addMethod(routes []*ffapi.Route, method *fftypes.FFIMet Path: fmt.Sprintf("query/%s", method.Pathname), // must match a route defined in apiserver routes! Method: http.MethodPost, JSONInputSchema: func(ctx context.Context, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) { - return contractJSONSchema(&method.Params, hasLocation) + return contractJSONSchema(ctx, &method.Params, hasLocation) }, JSONOutputSchema: func(ctx context.Context, schemaGen ffapi.SchemaGenerator) (*openapi3.SchemaRef, error) { - return contractJSONSchema(&method.Returns, true) + return contractJSONSchema(ctx, &method.Returns, true) }, JSONOutputCodes: []int{http.StatusOK}, PreTranslatedDescription: description, @@ -158,19 +157,29 @@ func (og *ffiSwaggerGen) addEvent(routes []*ffapi.Route, event *fftypes.FFIEvent * Parse the FFI and build a corresponding JSON Schema to describe the request body for "invoke". * Returns the JSON Schema as an `fftypes.JSONObject`. */ -func contractJSONSchema(params *fftypes.FFIParams, hasLocation bool) (*openapi3.SchemaRef, error) { +func contractJSONSchema(ctx context.Context, params *fftypes.FFIParams, hasLocation bool) (*openapi3.SchemaRef, error) { paramSchema := make(fftypes.JSONObject, len(*params)) for _, param := range *params { paramSchema[param.Name] = param.Schema } inputSchema := fftypes.JSONObject{ - "type": "object", - "properties": paramSchema, + "type": "object", + "description": i18n.Expand(ctx, coremsgs.ContractCallRequestInput), + "properties": paramSchema, } properties := fftypes.JSONObject{ "input": inputSchema, "options": fftypes.JSONObject{ - "type": "object", + "type": "object", + "description": i18n.Expand(ctx, coremsgs.ContractCallRequestOptions), + }, + "key": fftypes.JSONObject{ + "type": "string", + "description": i18n.Expand(ctx, coremsgs.ContractCallRequestKey), + }, + "idempotencyKey": fftypes.JSONObject{ + "type": "string", + "description": i18n.Expand(ctx, coremsgs.ContractCallIdempotencyKey), }, } if !hasLocation { diff --git a/internal/apiserver/ffi2swagger_test.go b/internal/apiserver/ffi2swagger_test.go index 54dfd4e02f..9f86245248 100644 --- a/internal/apiserver/ffi2swagger_test.go +++ b/internal/apiserver/ffi2swagger_test.go @@ -126,25 +126,25 @@ func TestGenerate(t *testing.T) { invokeMethod1 := doc.Paths["/invoke/method1"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", invokeMethod1.Type) - assert.ElementsMatch(t, []string{"input", "location", "options"}, paramNames(invokeMethod1.Properties)) + assert.ElementsMatch(t, []string{"input", "location", "options", "key", "idempotencyKey"}, paramNames(invokeMethod1.Properties)) assert.Equal(t, "object", invokeMethod1.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{"x", "y", "z"}, paramNames(invokeMethod1.Properties["input"].Value.Properties)) invokeMethod2 := doc.Paths["/invoke/method2"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", invokeMethod2.Type) - assert.ElementsMatch(t, []string{"input", "location", "options"}, paramNames(invokeMethod2.Properties)) + assert.ElementsMatch(t, []string{"input", "location", "options", "key", "idempotencyKey"}, paramNames(invokeMethod2.Properties)) assert.Equal(t, "object", invokeMethod2.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{}, paramNames(invokeMethod2.Properties["input"].Value.Properties)) queryMethod1 := doc.Paths["/query/method1"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", queryMethod1.Type) - assert.ElementsMatch(t, []string{"input", "location", "options"}, paramNames(queryMethod1.Properties)) + assert.ElementsMatch(t, []string{"input", "location", "options", "key", "idempotencyKey"}, paramNames(queryMethod1.Properties)) assert.Equal(t, "object", queryMethod1.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{"x", "y", "z"}, paramNames(queryMethod1.Properties["input"].Value.Properties)) queryMethod2 := doc.Paths["/query/method2"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", queryMethod2.Type) - assert.ElementsMatch(t, []string{"input", "location", "options"}, paramNames(queryMethod2.Properties)) + assert.ElementsMatch(t, []string{"input", "location", "options", "key", "idempotencyKey"}, paramNames(queryMethod2.Properties)) assert.Equal(t, "object", queryMethod2.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{}, paramNames(queryMethod2.Properties["input"].Value.Properties)) } @@ -162,37 +162,38 @@ func TestGenerateWithLocation(t *testing.T) { invokeMethod1 := doc.Paths["/invoke/method1"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", invokeMethod1.Type) - assert.ElementsMatch(t, []string{"input", "options"}, paramNames(invokeMethod1.Properties)) + assert.ElementsMatch(t, []string{"input", "options", "key", "idempotencyKey"}, paramNames(invokeMethod1.Properties)) assert.Equal(t, "object", invokeMethod1.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{"x", "y", "z"}, paramNames(invokeMethod1.Properties["input"].Value.Properties)) invokeMethod2 := doc.Paths["/invoke/method2"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", invokeMethod2.Type) - assert.ElementsMatch(t, []string{"input", "options"}, paramNames(invokeMethod2.Properties)) + assert.ElementsMatch(t, []string{"input", "options", "key", "idempotencyKey"}, paramNames(invokeMethod2.Properties)) assert.Equal(t, "object", invokeMethod2.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{}, paramNames(invokeMethod2.Properties["input"].Value.Properties)) queryMethod1 := doc.Paths["/query/method1"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", queryMethod1.Type) - assert.ElementsMatch(t, []string{"input", "options"}, paramNames(queryMethod1.Properties)) + assert.ElementsMatch(t, []string{"input", "options", "key", "idempotencyKey"}, paramNames(queryMethod1.Properties)) assert.Equal(t, "object", queryMethod1.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{"x", "y", "z"}, paramNames(queryMethod1.Properties["input"].Value.Properties)) queryMethod2 := doc.Paths["/query/method2"].Post.RequestBody.Value.Content.Get("application/json").Schema.Value assert.Equal(t, "object", queryMethod2.Type) - assert.ElementsMatch(t, []string{"input", "options"}, paramNames(queryMethod2.Properties)) + assert.ElementsMatch(t, []string{"input", "options", "key", "idempotencyKey"}, paramNames(queryMethod2.Properties)) assert.Equal(t, "object", queryMethod2.Properties["input"].Value.Type) assert.ElementsMatch(t, []string{}, paramNames(queryMethod2.Properties["input"].Value.Properties)) } func TestFFIParamBadSchema(t *testing.T) { + ctx := context.Background() params := &fftypes.FFIParams{ &fftypes.FFIParam{ Name: "test", Schema: fftypes.JSONAnyPtr(`{`), }, } - _, err := contractJSONSchema(params, true) + _, err := contractJSONSchema(ctx, params, true) assert.Error(t, err) params = &fftypes.FFIParams{ @@ -201,6 +202,6 @@ func TestFFIParamBadSchema(t *testing.T) { Schema: fftypes.JSONAnyPtr(`{"type": false}`), }, } - _, err = contractJSONSchema(params, true) + _, err = contractJSONSchema(ctx, params, true) assert.Error(t, err) }