generator: support required parameters with no parameterOrder key.

If a method has required parameters but no "parameterOrder" key, collect
the required params and make them args.

Since the JSON data is a map, the order of the parameters may change
from run to run, so sort them by name.

Change-Id: I2ce901b115e1821cbdf25b7cfc34b0445e7953cc
Reviewed-on: https://code-review.googlesource.com/32210
Reviewed-by: Jonathan Amsterdam <jba@google.com>
diff --git a/google-api-go-generator/gen.go b/google-api-go-generator/gen.go
index bc07142..48a8d71 100644
--- a/google-api-go-generator/gen.go
+++ b/google-api-go-generator/gen.go
@@ -1060,7 +1060,7 @@
 			case disco.StructKind:
 				addSubStruct(subApiName, p.Type())
 			default:
-				panicf("Unknown type for %s: %v", subApiName, p.Type())
+				panicf("Unknown type for %q: %v", subApiName, p.Type())
 			}
 		}
 	case disco.ArrayKind:
@@ -1081,7 +1081,7 @@
 		case disco.StructKind:
 			addSubStruct(subApiName, at)
 		default:
-			panicf("Unknown array type for %s: %v", subApiName, at)
+			panicf("Unknown array type for %q: %v", subApiName, at)
 		}
 	case disco.AnyStructKind, disco.MapKind, disco.SimpleKind, disco.ReferenceKind:
 		// Do nothing.
@@ -1440,7 +1440,7 @@
 	r   *disco.Resource // or nil if a API-level (top-level) method
 	m   *disco.Method
 
-	params []*Param // all Params, of each type, lazily set by first access to Parameters
+	params []*Param // all Params, of each type, lazily set by first call of Params method.
 }
 
 func (m *Method) Id() string {
@@ -2090,22 +2090,27 @@
 	return u.String()
 }
 
-func (meth *Method) NewArguments() (args *arguments) {
-	args = &arguments{
+func (meth *Method) NewArguments() *arguments {
+	args := &arguments{
 		method: meth,
 		m:      make(map[string]*argument),
 	}
-	po := meth.m.ParameterOrder
-	if len(po) > 0 {
-		for _, pname := range po {
-			arg := meth.NewArg(pname, meth.NamedParam(pname))
-			args.AddArg(arg)
+	pnames := meth.m.ParameterOrder
+	if len(pnames) == 0 {
+		// No parameterOrder; collect required parameters and sort by name.
+		for _, reqParam := range meth.grepParams(func(p *Param) bool { return p.p.Required }) {
+			pnames = append(pnames, reqParam.p.Name)
 		}
+		sort.Strings(pnames)
+	}
+	for _, pname := range pnames {
+		arg := meth.NewArg(pname, meth.NamedParam(pname))
+		args.AddArg(arg)
 	}
 	if rs := meth.m.Request; rs != nil {
 		args.AddArg(meth.NewBodyArg(rs))
 	}
-	return
+	return args
 }
 
 func (meth *Method) NewBodyArg(ds *disco.Schema) *argument {
diff --git a/google-api-go-generator/gen_test.go b/google-api-go-generator/gen_test.go
index b6f3850..3349650 100644
--- a/google-api-go-generator/gen_test.go
+++ b/google-api-go-generator/gen_test.go
@@ -43,6 +43,7 @@
 		"param-rename",
 		"quotednum",
 		"repeated",
+		"required-query",
 		"resource-named-service", // appengine/v1/appengine-api.json
 		"unfortunatedefaults",
 		"variants",
diff --git a/google-api-go-generator/testdata/required-query.json b/google-api-go-generator/testdata/required-query.json
new file mode 100644
index 0000000..537a4f5
--- /dev/null
+++ b/google-api-go-generator/testdata/required-query.json
@@ -0,0 +1,43 @@
+{
+  "name": "tshealth",
+  "discoveryVersion": "v1",
+  "id": "tshealth:v1",
+  "basePath": "/_ah/api/tshealth/v1/",
+  "baseUrl": "https://ths-prod.googleplex.com/_ah/api/tshealth/v1/",
+  "servicePath": "tshealth/v1/",
+  "resources": {
+    "techs": {
+      "methods": {
+        "count": {
+          "description": "Counts the number of techs matching the constraints.",
+          "httpMethod": "GET",
+          "id": "tshealth.techs.count",
+          "parameters": {
+            "manager": {
+              "location": "query",
+              "required": true,
+              "type": "string"
+            }
+          },
+          "path": "techs/count",
+          "response": {
+            "$ref": "Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse"
+          }
+        }
+      }
+    }
+  },
+  "schemas": {
+    "Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse": {
+      "description": "Response for a tech count request.",
+      "id": "Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse",
+      "properties": {
+        "count": {
+          "format": "int64",
+          "type": "string"
+        }
+      },
+      "type": "object"
+    }
+  }
+}
diff --git a/google-api-go-generator/testdata/required-query.want b/google-api-go-generator/testdata/required-query.want
new file mode 100644
index 0000000..760c55e
--- /dev/null
+++ b/google-api-go-generator/testdata/required-query.want
@@ -0,0 +1,239 @@
+// Package tshealth provides access to the .
+//
+// Usage example:
+//
+//   import "google.golang.org/api/tshealth/"
+//   ...
+//   tshealthService, err := tshealth.New(oauthHttpClient)
+package tshealth // import "google.golang.org/api/tshealth/"
+
+import (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	context "golang.org/x/net/context"
+	ctxhttp "golang.org/x/net/context/ctxhttp"
+	gensupport "google.golang.org/api/gensupport"
+	googleapi "google.golang.org/api/googleapi"
+	"io"
+	"net/http"
+	"net/url"
+	"strconv"
+	"strings"
+)
+
+// Always reference these packages, just in case the auto-generated code
+// below doesn't.
+var _ = bytes.NewBuffer
+var _ = strconv.Itoa
+var _ = fmt.Sprintf
+var _ = json.NewDecoder
+var _ = io.Copy
+var _ = url.Parse
+var _ = gensupport.MarshalJSON
+var _ = googleapi.Version
+var _ = errors.New
+var _ = strings.Replace
+var _ = context.Canceled
+var _ = ctxhttp.Do
+
+const apiId = "tshealth:v1"
+const apiName = "tshealth"
+const apiVersion = ""
+const basePath = "https://www.googleapis.com/_ah/api/tshealth/v1/"
+
+func New(client *http.Client) (*Service, error) {
+	if client == nil {
+		return nil, errors.New("client is nil")
+	}
+	s := &Service{client: client, BasePath: basePath}
+	s.Techs = NewTechsService(s)
+	return s, nil
+}
+
+type Service struct {
+	client    *http.Client
+	BasePath  string // API endpoint base URL
+	UserAgent string // optional additional User-Agent fragment
+
+	Techs *TechsService
+}
+
+func (s *Service) userAgent() string {
+	if s.UserAgent == "" {
+		return googleapi.UserAgent
+	}
+	return googleapi.UserAgent + " " + s.UserAgent
+}
+
+func NewTechsService(s *Service) *TechsService {
+	rs := &TechsService{s: s}
+	return rs
+}
+
+type TechsService struct {
+	s *Service
+}
+
+// Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResp
+// onse: Response for a tech count request.
+type Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse struct {
+	Count int64 `json:"count,omitempty,string"`
+
+	// ServerResponse contains the HTTP response code and headers from the
+	// server.
+	googleapi.ServerResponse `json:"-"`
+
+	// ForceSendFields is a list of field names (e.g. "Count") to
+	// unconditionally include in API requests. By default, fields with
+	// empty values are omitted from API requests. However, any non-pointer,
+	// non-interface field appearing in ForceSendFields will be sent to the
+	// server regardless of whether the field is empty or not. This may be
+	// used to include empty fields in Patch requests.
+	ForceSendFields []string `json:"-"`
+
+	// NullFields is a list of field names (e.g. "Count") to include in API
+	// requests with the JSON null value. By default, fields with empty
+	// values are omitted from API requests. However, any field with an
+	// empty value appearing in NullFields will be sent to the server as
+	// null. It is an error if a field in this list has a non-empty value.
+	// This may be used to include null fields in Patch requests.
+	NullFields []string `json:"-"`
+}
+
+func (s *Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse) MarshalJSON() ([]byte, error) {
+	type NoMethod Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse
+	raw := NoMethod(*s)
+	return gensupport.MarshalJSON(raw, s.ForceSendFields, s.NullFields)
+}
+
+// method id "tshealth.techs.count":
+
+type TechsCountCall struct {
+	s            *Service
+	urlParams_   gensupport.URLParams
+	ifNoneMatch_ string
+	ctx_         context.Context
+	header_      http.Header
+}
+
+// Count: Counts the number of techs matching the constraints.
+func (r *TechsService) Count(manager string) *TechsCountCall {
+	c := &TechsCountCall{s: r.s, urlParams_: make(gensupport.URLParams)}
+	c.urlParams_.Set("manager", manager)
+	return c
+}
+
+// Fields allows partial responses to be retrieved. See
+// https://developers.google.com/gdata/docs/2.0/basics#PartialResponse
+// for more information.
+func (c *TechsCountCall) Fields(s ...googleapi.Field) *TechsCountCall {
+	c.urlParams_.Set("fields", googleapi.CombineFields(s))
+	return c
+}
+
+// IfNoneMatch sets the optional parameter which makes the operation
+// fail if the object's ETag matches the given value. This is useful for
+// getting updates only after the object has changed since the last
+// request. Use googleapi.IsNotModified to check whether the response
+// error from Do is the result of In-None-Match.
+func (c *TechsCountCall) IfNoneMatch(entityTag string) *TechsCountCall {
+	c.ifNoneMatch_ = entityTag
+	return c
+}
+
+// Context sets the context to be used in this call's Do method. Any
+// pending HTTP request will be aborted if the provided context is
+// canceled.
+func (c *TechsCountCall) Context(ctx context.Context) *TechsCountCall {
+	c.ctx_ = ctx
+	return c
+}
+
+// Header returns an http.Header that can be modified by the caller to
+// add HTTP headers to the request.
+func (c *TechsCountCall) Header() http.Header {
+	if c.header_ == nil {
+		c.header_ = make(http.Header)
+	}
+	return c.header_
+}
+
+func (c *TechsCountCall) doRequest(alt string) (*http.Response, error) {
+	reqHeaders := make(http.Header)
+	for k, v := range c.header_ {
+		reqHeaders[k] = v
+	}
+	reqHeaders.Set("User-Agent", c.s.userAgent())
+	if c.ifNoneMatch_ != "" {
+		reqHeaders.Set("If-None-Match", c.ifNoneMatch_)
+	}
+	var body io.Reader = nil
+	c.urlParams_.Set("alt", alt)
+	c.urlParams_.Set("prettyPrint", "false")
+	urls := googleapi.ResolveRelative(c.s.BasePath, "techs/count")
+	urls += "?" + c.urlParams_.Encode()
+	req, _ := http.NewRequest("GET", urls, body)
+	req.Header = reqHeaders
+	return gensupport.SendRequest(c.ctx_, c.s.client, req)
+}
+
+// Do executes the "tshealth.techs.count" call.
+// Exactly one of
+// *Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountRes
+// ponse or error will be non-nil. Any non-2xx status code is an error.
+// Response headers are in either
+// *Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountRes
+// ponse.ServerResponse.Header or (if a response was returned at all) in
+// error.(*googleapi.Error).Header. Use googleapi.IsNotModified to check
+// whether the returned error was because http.StatusNotModified was
+// returned.
+func (c *TechsCountCall) Do(opts ...googleapi.CallOption) (*Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse, error) {
+	gensupport.SetOptions(c.urlParams_, opts...)
+	res, err := c.doRequest("json")
+	if res != nil && res.StatusCode == http.StatusNotModified {
+		if res.Body != nil {
+			res.Body.Close()
+		}
+		return nil, &googleapi.Error{
+			Code:   res.StatusCode,
+			Header: res.Header,
+		}
+	}
+	if err != nil {
+		return nil, err
+	}
+	defer googleapi.CloseBody(res)
+	if err := googleapi.CheckResponse(res); err != nil {
+		return nil, err
+	}
+	ret := &Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse{
+		ServerResponse: googleapi.ServerResponse{
+			Header:         res.Header,
+			HTTPStatusCode: res.StatusCode,
+		},
+	}
+	target := &ret
+	if err := gensupport.DecodeResponse(target, res); err != nil {
+		return nil, err
+	}
+	return ret, nil
+	// {
+	//   "description": "Counts the number of techs matching the constraints.",
+	//   "httpMethod": "GET",
+	//   "id": "tshealth.techs.count",
+	//   "parameters": {
+	//     "manager": {
+	//       "location": "query",
+	//       "required": true,
+	//       "type": "string"
+	//     }
+	//   },
+	//   "path": "techs/count",
+	//   "response": {
+	//     "$ref": "Google3CorpSupportToolsTshealthServiceApiV1TechsMessagesTechsCountResponse"
+	//   }
+	// }
+
+}