Repository: google/go-github (go)

Issue:
`MetaService.Get` fails because the schema of the JSON in the API response is different from the expected in `APIMeta.Domains` type
Downstream issue: https://github.com/integrations/terraform-provider-github/issues/2357

GitHub has a meta information API, and go-github has a corresponding implementation:

- https://docs.github.com/en/rest/meta/meta?apiVersion=2022-11-28#get-github-meta-information
  - Can be called in https://api.github.com/meta
- https://pkg.go.dev/github.com/google/go-github/v63/github#MetaService
  - https://github.com/google/go-github/blob/882755a047ea3e09b2f5f4a0e888d9a12cf2852f/github/meta.go

In the response of this API endpoint, the JSON unmarshal always seems to fail for the `Domains` field -- because the type of the value is different from what is expected.

In go-github, values under `domains` property is expected to be `map[string][]string`:

https://github.com/google/go-github/blob/882755a047ea3e09b2f5f4a0e888d9a12cf2852f/github/meta.go#L73-L75

But it actually looks like this (see https://api.github.com/meta):

```
{
  ... snip ...,
  "domains": {
    "website": [ "*.github.com", ... snip ... ],
    "codespaces": [ "*.github.com", ... snip ... ],
    "copilot": [ "*.github.com", ... snip ... ],
    "packages": [ "mavenregistryv2prod.blob.core.windows.net", ... snip ... ],
    "actions": [ "*.actions.githubusercontent.com", ... snip ... ],
    "artifact_attestations": {
      "trust_domain": "",
      "services": [ "*.actions.githubusercontent.com", ... snip ... ]
    }
  }
}
```

The values under `artifact_attestations` field clearly deviates from the `[]string` type.

This incompatibility causes `MetaService.Get()` to fail with errors like `Error: json: cannot unmarshal object into Go struct field APIMeta.domains of type []string`. 

Pull request:
feat!: Fix meta service domains schema structure
Fixes: #3248.

BREAKING CHANGE: `APIMeta.Domains` changed from `map` to `struct`.

This should at least be a tiny bit less prone to breaking, as it only accounts for existing schema with that API, and future additions should silently be ignored (unless GitHub breaks the contract, which they didn't _really_ do in this scenario).

- closes #3248

Patch:
diff --git a/github/github-accessors.go b/github/github-accessors.go
index 66e9c0ba..10096e44 100644
--- a/github/github-accessors.go
+++ b/github/github-accessors.go
@@ -662,6 +662,14 @@ func (a *AnalysesListOptions) GetSarifID() string {
 	return *a.SarifID
 }
 
+// GetDomains returns the Domains field.
+func (a *APIMeta) GetDomains() *APIMetaDomains {
+	if a == nil {
+		return nil
+	}
+	return a.Domains
+}
+
 // GetSSHKeyFingerprints returns the SSHKeyFingerprints map if it's non-nil, an empty map otherwise.
 func (a *APIMeta) GetSSHKeyFingerprints() map[string]string {
 	if a == nil || a.SSHKeyFingerprints == nil {
@@ -678,6 +686,14 @@ func (a *APIMeta) GetVerifiablePasswordAuthentication() bool {
 	return *a.VerifiablePasswordAuthentication
 }
 
+// GetArtifactAttestations returns the ArtifactAttestations field.
+func (a *APIMetaDomains) GetArtifactAttestations() *APIMetaArtifactAttestations {
+	if a == nil {
+		return nil
+	}
+	return a.ArtifactAttestations
+}
+
 // GetCreatedAt returns the CreatedAt field if it's non-nil, zero value otherwise.
 func (a *App) GetCreatedAt() Timestamp {
 	if a == nil || a.CreatedAt == nil {
diff --git a/github/meta.go b/github/meta.go
index e0e355e8..cc90b618 100644
--- a/github/meta.go
+++ b/github/meta.go
@@ -70,9 +70,25 @@ type APIMeta struct {
 	// which serve GitHub APIs.
 	API []string `json:"api,omitempty"`
 
-	// A map of GitHub services and their associated domains. Note that many
-	// of these domains are represented as wildcards (e.g. "*.github.com").
-	Domains map[string][]string `json:"domains,omitempty"`
+	// GitHub services and their associated domains. Note that many of these domains
+	// are represented as wildcards (e.g. "*.github.com").
+	Domains *APIMetaDomains `json:"domains,omitempty"`
+}
+
+// APIMetaDomains represents the domains associated with GitHub services.
+type APIMetaDomains struct {
+	Website              []string                     `json:"website,omitempty"`
+	Codespaces           []string                     `json:"codespaces,omitempty"`
+	Copilot              []string                     `json:"copilot,omitempty"`
+	Packages             []string                     `json:"packages,omitempty"`
+	Actions              []string                     `json:"actions,omitempty"`
+	ArtifactAttestations *APIMetaArtifactAttestations `json:"artifact_attestations,omitempty"`
+}
+
+// APIMetaArtifactAttestations represents the artifact attestation services domains.
+type APIMetaArtifactAttestations struct {
+	TrustDomain string   `json:"trust_domain,omitempty"`
+	Services    []string `json:"services,omitempty"`
 }
 
 // Get returns information about GitHub.com, the service. Or, if you access


Test patch:
diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go
index dde2c80b..aa694fd5 100644
--- a/github/github-accessors_test.go
+++ b/github/github-accessors_test.go
@@ -765,6 +765,13 @@ func TestAnalysesListOptions_GetSarifID(tt *testing.T) {
 	a.GetSarifID()
 }
 
+func TestAPIMeta_GetDomains(tt *testing.T) {
+	a := &APIMeta{}
+	a.GetDomains()
+	a = nil
+	a.GetDomains()
+}
+
 func TestAPIMeta_GetSSHKeyFingerprints(tt *testing.T) {
 	zeroValue := map[string]string{}
 	a := &APIMeta{SSHKeyFingerprints: zeroValue}
@@ -785,6 +792,13 @@ func TestAPIMeta_GetVerifiablePasswordAuthentication(tt *testing.T) {
 	a.GetVerifiablePasswordAuthentication()
 }
 
+func TestAPIMetaDomains_GetArtifactAttestations(tt *testing.T) {
+	a := &APIMetaDomains{}
+	a.GetArtifactAttestations()
+	a = nil
+	a.GetArtifactAttestations()
+}
+
 func TestApp_GetCreatedAt(tt *testing.T) {
 	var zeroValue Timestamp
 	a := &App{CreatedAt: &zeroValue}
diff --git a/github/meta_test.go b/github/meta_test.go
index 99cb0b02..836d9931 100644
--- a/github/meta_test.go
+++ b/github/meta_test.go
@@ -30,8 +30,23 @@ func TestAPIMeta_Marshal(t *testing.T) {
 		SSHKeys:                          []string{"k"},
 		API:                              []string{"a"},
 		Web:                              []string{"w"},
-		Domains: map[string][]string{
-			"example": {"example.com", "*.example.com"},
+		Domains: &APIMetaDomains{
+			Website: []string{
+				"*.github.com",
+				"*.github.dev",
+				"*.github.io",
+				"*.githubassets.com",
+				"*.githubusercontent.com",
+			},
+			ArtifactAttestations: &APIMetaArtifactAttestations{
+				TrustDomain: "",
+				Services: []string{
+					"*.actions.githubusercontent.com",
+					"tuf-repo.github.com",
+					"fulcio.githubapp.com",
+					"timestamp.githubapp.com",
+				},
+			},
 		},
 	}
 	want := `{
@@ -47,7 +62,7 @@ func TestAPIMeta_Marshal(t *testing.T) {
 		"ssh_keys":["k"],
 		"api":["a"],
 		"web":["w"],
-		"domains":{"example":["example.com","*.example.com"]}
+		"domains":{"website":["*.github.com","*.github.dev","*.github.io","*.githubassets.com","*.githubusercontent.com"],"artifact_attestations":{"trust_domain":"","services":["*.actions.githubusercontent.com","tuf-repo.github.com","fulcio.githubapp.com","timestamp.githubapp.com"]}}
 	}`
 
 	testJSONMarshal(t, a, want)
@@ -59,7 +74,7 @@ func TestMetaService_Get(t *testing.T) {
 
 	mux.HandleFunc("/meta", func(w http.ResponseWriter, r *http.Request) {
 		testMethod(t, r, "GET")
-		fmt.Fprint(w, `{"web":["w"],"api":["a"],"hooks":["h"], "git":["g"], "pages":["p"], "importer":["i"], "github_enterprise_importer": ["gei"], "actions":["a"], "dependabot":["d"], "verifiable_password_authentication": true, "domains":{"example":["example.com","*.example.com"]}}`)
+		fmt.Fprint(w, `{"web":["w"],"api":["a"],"hooks":["h"], "git":["g"], "pages":["p"], "importer":["i"], "github_enterprise_importer": ["gei"], "actions":["a"], "dependabot":["d"], "verifiable_password_authentication": true, "domains":{"website":["*.github.com","*.github.dev","*.github.io","*.githubassets.com","*.githubusercontent.com"],"artifact_attestations":{"trust_domain":"","services":["*.actions.githubusercontent.com","tuf-repo.github.com","fulcio.githubapp.com","timestamp.githubapp.com"]}}}`)
 	})
 
 	ctx := context.Background()
@@ -78,8 +93,23 @@ func TestMetaService_Get(t *testing.T) {
 		Dependabot:               []string{"d"},
 		API:                      []string{"a"},
 		Web:                      []string{"w"},
-		Domains: map[string][]string{
-			"example": {"example.com", "*.example.com"},
+		Domains: &APIMetaDomains{
+			Website: []string{
+				"*.github.com",
+				"*.github.dev",
+				"*.github.io",
+				"*.githubassets.com",
+				"*.githubusercontent.com",
+			},
+			ArtifactAttestations: &APIMetaArtifactAttestations{
+				TrustDomain: "",
+				Services: []string{
+					"*.actions.githubusercontent.com",
+					"tuf-repo.github.com",
+					"fulcio.githubapp.com",
+					"timestamp.githubapp.com",
+				},
+			},
 		},
 
 		VerifiablePasswordAuthentication: Bool(true),