Repository: apigee/registry (go)

Issue:
Registry tool: add registry rules for ApiSpec
Add ApiSpec resource checks to `registry check` command.

rule group | rule number | resource | field | type | severity | check
-- | -- | -- | -- | -- | -- | --
Registry | 105 | ApiSpec | source_uri | string | ERROR | valid URI format


Pull request description:
add registry rules for ApiSpec
Fixes #954

Patch:
diff --git a/cmd/registry/cmd/check/rules/rule105/rule105.go b/cmd/registry/cmd/check/rules/rule105/rule105.go
new file mode 100644
index 0000000..f7f0e9a
--- /dev/null
+++ b/cmd/registry/cmd/check/rules/rule105/rule105.go
@@ -0,0 +1,53 @@
+// Copyright 2023 Google LLC. All Rights Reserved.
+//
+// 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 rule105
+
+import (
+	"context"
+	"net/url"
+
+	"github.com/apigee/registry/cmd/registry/cmd/check/lint"
+	"github.com/apigee/registry/rpc"
+)
+
+var ruleNum = 105
+var ruleName = lint.NewRuleName(ruleNum, "apispec-sourceuri-format")
+
+// AddRules accepts a register function and registers each of
+// this rules' checks to the RuleRegistry.
+func AddRules(r lint.RuleRegistry) error {
+	return r.Register(
+		ruleNum,
+		sourceUriFormat,
+	)
+}
+
+var sourceUriFormat = &lint.ApiSpecRule{
+	Name: ruleName,
+	OnlyIf: func(a *rpc.ApiSpec) bool {
+		return a.SourceUri != ""
+	},
+	ApplyToApiSpec: func(ctx context.Context, a *rpc.ApiSpec) []lint.Problem {
+		u, err := url.ParseRequestURI(a.SourceUri)
+		if err != nil || u.Host == "" {
+			return []lint.Problem{{
+				Severity:   lint.ERROR,
+				Message:    `source_uri must be an absolute URI.`,
+				Suggestion: `Ensure source_uri includes a host.`,
+			}}
+		}
+		return nil
+	},
+}
diff --git a/cmd/registry/cmd/check/rules/rules.go b/cmd/registry/cmd/check/rules/rules.go
index e9a4a12..d81533b 100644
--- a/cmd/registry/cmd/check/rules/rules.go
+++ b/cmd/registry/cmd/check/rules/rules.go
@@ -38,6 +38,7 @@ import (
 	"github.com/apigee/registry/cmd/registry/cmd/check/rules/rule102"
 	"github.com/apigee/registry/cmd/registry/cmd/check/rules/rule103"
 	"github.com/apigee/registry/cmd/registry/cmd/check/rules/rule104"
+	"github.com/apigee/registry/cmd/registry/cmd/check/rules/rule105"
 	"github.com/apigee/registry/cmd/registry/cmd/check/rules/rule110"
 )
 
@@ -49,6 +50,7 @@ var addRulesFuncs = []addRulesFuncType{
 	rule102.AddRules,
 	rule103.AddRules,
 	rule104.AddRules,
+	rule105.AddRules,
 	rule110.AddRules,
 }
 


Test patch:
diff --git a/cmd/registry/cmd/check/rules/rule105/rule105_test.go b/cmd/registry/cmd/check/rules/rule105/rule105_test.go
new file mode 100644
index 0000000..8bd8085
--- /dev/null
+++ b/cmd/registry/cmd/check/rules/rule105/rule105_test.go
@@ -0,0 +1,63 @@
+// Copyright 2023 Google LLC. All Rights Reserved.
+//
+// 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 rule105
+
+import (
+	"context"
+	"testing"
+
+	"github.com/apigee/registry/cmd/registry/cmd/check/lint"
+	"github.com/apigee/registry/rpc"
+	"github.com/google/go-cmp/cmp"
+)
+
+func TestAddRules(t *testing.T) {
+	if err := AddRules(lint.NewRuleRegistry()); err != nil {
+		t.Errorf("AddRules got an error: %v", err)
+	}
+}
+
+func Test_sourceUriFormat(t *testing.T) {
+	prob := []lint.Problem{{
+		Severity:   lint.ERROR,
+		Message:    `source_uri must be an absolute URI.`,
+		Suggestion: `Ensure source_uri includes a host.`,
+	}}
+
+	for _, tt := range []struct {
+		in       string
+		expected []lint.Problem
+	}{
+		{"", nil},
+		{"x", prob},
+		{"not a uri", prob},
+		{"http://localhost", nil},
+		{"http://127.0.0.1", nil},
+		{"http://ok/ok", nil},
+		{"https://google.com/ok#yes?ok=true", nil},
+	} {
+		t.Run(tt.in, func(t *testing.T) {
+			a := &rpc.ApiSpec{
+				SourceUri: tt.in,
+			}
+			if sourceUriFormat.OnlyIf(a) {
+				got := sourceUriFormat.ApplyToApiSpec(context.Background(), a)
+				if diff := cmp.Diff(got, tt.expected); diff != "" {
+					t.Errorf("unexpected diff: (-want +got):\n%s", diff)
+				}
+			}
+		})
+	}
+}