From 94e2dbd7c6e7092b49d683a06d0cd4f755456895 Mon Sep 17 00:00:00 2001
From: David Bloss <david@opslevel.com>
Date: Tue, 7 Jan 2025 09:15:34 -0600
Subject: [PATCH] generate unions (#505)

* generate unions

* drop unused TagOwner enums

* union tpl clean up
---
 check.go             |  5 ----
 document.go          |  5 ----
 gen.go               | 42 ++++++++++++++++++---------------
 owner.go             |  4 ----
 tags.go              |  7 ------
 templates/unions.tpl | 40 +++++++++++++++++++++++++++++++
 union.go             | 56 ++++++++++++++++++++++++++++++++++++++++++++
 7 files changed, 119 insertions(+), 40 deletions(-)
 create mode 100644 templates/unions.tpl
 create mode 100644 union.go

diff --git a/check.go b/check.go
index f3a78845..716adbfc 100644
--- a/check.go
+++ b/check.go
@@ -10,11 +10,6 @@ import (
 	"github.com/relvacode/iso8601"
 )
 
-type CheckOwner struct {
-	Team TeamId `graphql:"... on Team"`
-	// User User `graphql:"... on User"` // TODO: will this be public?
-}
-
 type CheckInputConstructor func() any
 
 var CheckCreateConstructors = map[CheckType]CheckInputConstructor{
diff --git a/document.go b/document.go
index 80316aab..7ca62cb8 100644
--- a/document.go
+++ b/document.go
@@ -1,10 +1,5 @@
 package opslevel
 
-type ServiceDocumentSource struct {
-	IntegrationId     `graphql:"... on ApiDocIntegration"`
-	ServiceRepository `graphql:"... on ServiceRepository"`
-}
-
 type ServiceDocument struct {
 	Id         ID                    `graphql:"id" json:"id"`
 	HtmlURL    string                `graphql:"htmlUrl" json:"htmUrl,omitempty"`
diff --git a/gen.go b/gen.go
index 9a0593d6..fe9934de 100644
--- a/gen.go
+++ b/gen.go
@@ -32,7 +32,6 @@ const (
 	mutationFile   string = "mutation.go"
 	payloadFile    string = "payload.go"
 	// scalarFile      string = "scalar.go" // NOTE: probably not useful
-	// unionFile       string = "union.go" // NOTE: probably not useful
 )
 
 var knownTypeIsName = []string{
@@ -285,6 +284,7 @@ func main() {
 			panic(fmt.Errorf("Unknown GraphQL type: %v", v))
 		}
 	}
+	genUnions(unions)
 	genObjects(objects)
 	genEnums(schemaAst.Enums)
 	genInputObjects(inputObjects)
@@ -310,6 +310,28 @@ func sortedMapKeys[T any](schemaMap map[string]T) []string {
 	return sortedNames
 }
 
+func genUnions(unions map[string]*types.Union) {
+	var buf bytes.Buffer
+
+	buf.WriteString(header + "\n\n")
+
+	tmpl := template.New("unions")
+	tmpl.Funcs(sprig.TxtFuncMap())
+	tmpl.Funcs(templFuncMap)
+	template.Must(tmpl.ParseFiles("./templates/unions.tpl"))
+	for _, union := range sortedMapKeys(unions) {
+		if err := tmpl.ExecuteTemplate(&buf, "union", unions[union]); err != nil {
+			panic(err)
+		}
+	}
+
+	fmt.Println("writing union.go")
+	err := os.WriteFile("union.go", buf.Bytes(), 0o644)
+	if err != nil {
+		panic(err)
+	}
+}
+
 func genEnums(schemaEnums []*types.EnumTypeDefinition) {
 	var buf bytes.Buffer
 
@@ -472,8 +494,6 @@ func run() error {
 			subSchema = objectSchema
 		// case scalarFile:
 		// 	subSchema = scalarSchema
-		// case unionFile:
-		// 	subSchema = unionSchema
 		default:
 			panic("Unknown file: " + filename)
 		}
@@ -867,21 +887,6 @@ var templates = map[string]*template.Template{
 	// func NewString(value string) *string {
 	// 	return &value
 	// }`),
-	// 	unionFile: t(header + `
-	// {{range .Types | sortByName}}{{if and (eq .Kind "UNION") (not (internal .Name))}}
-	// {{template "union_object" .}}
-	// {{end}}{{end}}
-
-	// {{- define "union_object" -}}
-	// // Union{{.Name}} {{ template "description" . }}
-	// type Union{{.Name}} interface { {{range .PossibleTypes }}
-	//
-	//	    {{.Name}}Fragment() {{.Name}}Fragment{{end}}
-	//	}
-	//
-	// {{- end -}}
-	//
-	//	`),
 }
 
 func t(text string) *template.Template {
@@ -1252,7 +1257,6 @@ func firstCharLowered(s string) string {
 var templFuncMap = template.FuncMap{
 	"internal":                            func(s string) bool { return strings.HasPrefix(s, "__") },
 	"quote":                               strconv.Quote,
-	"join":                                strings.Join,
 	"check_fragments":                     fragmentsForCheck,
 	"custom_actions_ext_action_fragments": fragmentsForCustomActionsExtAction,
 	"integration_fragments":               fragmentsForIntegration,
diff --git a/owner.go b/owner.go
index 304ab664..050db146 100644
--- a/owner.go
+++ b/owner.go
@@ -5,10 +5,6 @@ type EntityOwnerTeam struct {
 	Id    ID     `json:"id"`
 }
 
-type EntityOwner struct {
-	OnTeam EntityOwnerTeam `graphql:"... on Team"`
-}
-
 func (entityOwner *EntityOwner) Alias() string {
 	return entityOwner.OnTeam.Alias
 }
diff --git a/tags.go b/tags.go
index 9f106232..0c302430 100644
--- a/tags.go
+++ b/tags.go
@@ -7,13 +7,6 @@ import (
 	"slices"
 )
 
-type TagOwner string
-
-const (
-	TagOwnerService    TagOwner = "Service"
-	TagOwnerRepository TagOwner = "Repository"
-)
-
 type TaggableResourceInterface interface {
 	GetTags(*Client, *PayloadVariables) (*TagConnection, error)
 	ResourceId() ID
diff --git a/templates/unions.tpl b/templates/unions.tpl
new file mode 100644
index 00000000..9e7ab896
--- /dev/null
+++ b/templates/unions.tpl
@@ -0,0 +1,40 @@
+{{- define "union" }}
+// {{ title .Name }} {{.Desc | clean | endSentence}}
+  {{- if eq "CheckOwner" .Name }}
+    {{ template "check_owner" }}
+  {{- else if eq "EntityOwner" .Name }}
+    {{ template "entity_owner" }}
+  {{- else if eq "ServiceDocumentSource" .Name }}
+    {{ template "service_document_source" }}
+  {{- else }}
+type {{.Name}} struct {
+ {{ range .TypeNames }}
+    {{- if not (contains "Group" . ) }}
+  {{.}} {{. -}}
+      {{- if not (contains . (list "ApiDocIntegration" "InfrastructureResource" "ServiceRepository" | join " " )) -}}Id
+      {{- end }} `graphql:"... on {{.}}"`
+    {{- end }}
+  {{- end }}
+}
+ {{- end }}
+{{- end -}}
+
+{{- define "check_owner" -}}
+type CheckOwner struct {
+	Team TeamId `graphql:"... on Team"`
+	// User UserId `graphql:"... on User"` // TODO: will this be public?
+}
+{{ end }}
+
+{{- define "entity_owner" -}}
+type EntityOwner struct {
+	OnTeam EntityOwnerTeam `graphql:"... on Team"`
+}
+{{ end }}
+
+{{- define "service_document_source" -}}
+type ServiceDocumentSource struct {
+	IntegrationId     `graphql:"... on ApiDocIntegration"`
+	ServiceRepository `graphql:"... on ServiceRepository"`
+}
+{{ end }}
diff --git a/union.go b/union.go
new file mode 100644
index 00000000..ccbc7632
--- /dev/null
+++ b/union.go
@@ -0,0 +1,56 @@
+// Code generated by gen.go; DO NOT EDIT.
+
+package opslevel
+
+// CheckOwner represents the owner a check can belong to.
+type CheckOwner struct {
+	Team TeamId `graphql:"... on Team"`
+	// User UserId `graphql:"... on User"` // TODO: will this be public?
+}
+
+// CodeIssueProjectResource represents resource linked to the CodeIssueProject. Can be either Service or Repository.
+type CodeIssueProjectResource struct {
+	Repository Repository `graphql:"... on Repository"`
+	Service    Service    `graphql:"... on Service"`
+}
+
+// ContactOwner represents the owner of this contact.
+type ContactOwner struct {
+	Team TeamId `graphql:"... on Team"`
+	User UserId `graphql:"... on User"`
+}
+
+// CustomActionsAssociatedObject represents the object that an event was triggered on.
+type CustomActionsAssociatedObject struct {
+	Service Service `graphql:"... on Service"`
+}
+
+// EntityOwner represents the Group or Team owning the entity.
+type EntityOwner struct {
+	OnTeam EntityOwnerTeam `graphql:"... on Team"`
+}
+
+// RelationshipResource represents a resource that can have relationships to other resources.
+type RelationshipResource struct {
+	Domain                 DomainId               `graphql:"... on Domain"`
+	InfrastructureResource InfrastructureResource `graphql:"... on InfrastructureResource"`
+	Service                Service                `graphql:"... on Service"`
+	System                 SystemId               `graphql:"... on System"`
+}
+
+// ServiceDocumentSource represents the source of a document.
+type ServiceDocumentSource struct {
+	IntegrationId     `graphql:"... on ApiDocIntegration"`
+	ServiceRepository `graphql:"... on ServiceRepository"`
+}
+
+// TagOwner represents a resource that a tag can be applied to.
+type TagOwner struct {
+	Domain                 DomainId               `graphql:"... on Domain"`
+	InfrastructureResource InfrastructureResource `graphql:"... on InfrastructureResource"`
+	Repository             Repository             `graphql:"... on Repository"`
+	Service                Service                `graphql:"... on Service"`
+	System                 SystemId               `graphql:"... on System"`
+	Team                   TeamId                 `graphql:"... on Team"`
+	User                   UserId                 `graphql:"... on User"`
+}