Repository: deepmap/oapi-codegen (go)

Issue:
Generate default operationId if none provided in spec
Is it possible to create a default `operationId` using a combination of the Verb & Path if the `operationId` is not provided in the spec? More often than not the author of the original spec will use exactly that semantic to generate the `operationId `name. Rather than error out [here](https://github.com/deepmap/oapi-codegen/blob/master/pkg/codegen/operations.go#L389-L392) just generate a default name?

The scenario I am faced is that a lot of the legacy specs, in my organization, in heavy use were authored without the need for `operationId` (since the codegen for most of the code only generated types). If this could be supported, that would be great IMO.

Pull request description:
Generate default operationId if none provided
Fixes #62

Patch:
diff --git a/pkg/codegen/operations.go b/pkg/codegen/operations.go
index d3fb3cc..28f39b4 100644
--- a/pkg/codegen/operations.go
+++ b/pkg/codegen/operations.go
@@ -388,7 +388,11 @@ func OperationDefinitions(swagger *openapi3.Swagger) ([]OperationDefinition, err
 
 			// We rely on OperationID to generate function names, it's required
 			if op.OperationID == "" {
-				return nil, fmt.Errorf("OperationId is missing on path '%s %s'", opName, requestPath)
+				op.OperationID, err = generateDefaultOperationID(opName, requestPath)
+				if err != nil {
+					return nil, fmt.Errorf("error generating default OperationID for %s/%s: %s",
+						opName, requestPath, err)
+				}
 			}
 
 			// These are parameters defined for the specific path method that
@@ -448,6 +452,26 @@ func OperationDefinitions(swagger *openapi3.Swagger) ([]OperationDefinition, err
 	return operations, nil
 }
 
+func generateDefaultOperationID(opName string, requestPath string) (string, error) {
+	var operationId string = strings.ToLower(opName)
+
+	if opName == "" {
+		return "", fmt.Errorf("operation name cannot be an empty string")
+	}
+
+	if requestPath == "" {
+		return "", fmt.Errorf("request path cannot be an empty string")
+	}
+
+	for _, part := range strings.Split(requestPath, "/") {
+		if part != "" {
+			operationId = operationId + "-" + part
+		}
+	}
+
+	return ToCamelCase(operationId), nil
+}
+
 // This function turns the Swagger body definitions into a list of our body
 // definitions which will be used for code generation.
 func GenerateBodyDefinitions(operationID string, bodyOrRef *openapi3.RequestBodyRef) ([]RequestBodyDefinition, []TypeDefinition, error) {


Test patch:
diff --git a/pkg/codegen/operations_test.go b/pkg/codegen/operations_test.go
new file mode 100644
index 0000000..0636f29
--- /dev/null
+++ b/pkg/codegen/operations_test.go
@@ -0,0 +1,81 @@
+// Copyright 2019 DeepMap, Inc.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+package codegen
+
+import (
+	"net/http"
+	"testing"
+)
+
+func TestGenerateDefaultOperationID(t *testing.T) {
+	type test struct {
+		op string
+		path string
+		want string
+		wantErr bool
+	}
+
+	suite := []test {
+		{
+			op: http.MethodGet,
+			path: "/v1/foo/bar",
+			want: "GetV1FooBar",
+			wantErr: false,
+		},
+		{
+			op: http.MethodGet,
+			path: "/v1/foo/bar/",
+			want: "GetV1FooBar",
+			wantErr: false,
+		},
+		{
+			op: http.MethodPost,
+			path: "/v1",
+			want: "PostV1",
+			wantErr: false,
+		},
+		{
+			op: http.MethodPost,
+			path: "v1",
+			want: "PostV1",
+			wantErr: false,
+		},
+		{
+			path: "v1",
+			want: "",
+			wantErr: true,
+		},
+		{
+			path: "",
+			want: "PostV1",
+			wantErr: true,
+		},
+	}
+
+	for _, test := range suite {
+		got, err := generateDefaultOperationID(test.op, test.path)
+		if err != nil {
+			if !test.wantErr {
+				t.Fatalf("did not expected error but got %v", err)
+			}
+		}
+
+		if test.wantErr	{
+			return
+		}
+		if got != test.want {
+			t.Fatalf("Operation ID generation error. Want [%v] Got [%v]", test.want, got)
+		}
+	}
+}
\ No newline at end of file