f4f83fb4创建于 7月30日历史提交
package gitcode

import (
	"fmt"
	"sort"
)

type ProductArea string

const (
	ProductAreaIssues       ProductArea = "issues"
	ProductAreaLabels       ProductArea = "labels"
	ProductAreaMilestones   ProductArea = "milestones"
	ProductAreaPullRequests ProductArea = "pull_requests"
	ProductAreaComments     ProductArea = "comments"
	ProductAreaWiki         ProductArea = "wiki"
	ProductAreaPushMirrors  ProductArea = "push_mirrors"
)

func validProductArea(area ProductArea) bool {
	switch area {
	case ProductAreaIssues, ProductAreaLabels, ProductAreaMilestones, ProductAreaPullRequests, ProductAreaComments, ProductAreaWiki, ProductAreaPushMirrors:
		return true
	}
	return false
}

type SupportStatus string

const (
	SupportStatusSupported   SupportStatus = "supported"
	SupportStatusDeferred    SupportStatus = "deferred"
	SupportStatusUnsupported SupportStatus = "unsupported"
)

func validSupportStatus(status SupportStatus) bool {
	switch status {
	case SupportStatusSupported, SupportStatusDeferred, SupportStatusUnsupported:
		return true
	}
	return false
}

type EvidenceClass string

const (
	EvidenceClassOpenAPI   EvidenceClass = "OpenAPI"
	EvidenceClassLiveProbe EvidenceClass = "live_probe"
	EvidenceClassDeferred  EvidenceClass = "deferred"
)

func validEvidenceClass(ev EvidenceClass) bool {
	switch ev {
	case EvidenceClassOpenAPI, EvidenceClassLiveProbe, EvidenceClassDeferred:
		return true
	}
	return false
}

type RouteFamily string

const RouteFamilyAPIV5 RouteFamily = "/api/v5"

type UnsupportedDiagnostic struct {
	Code          string
	CapabilityKey string
	Message       string
}

type SurfaceSpec struct {
	Area       ProductArea
	Status     SupportStatus
	Route      RouteFamily
	Evidence   EvidenceClass
	Diagnostic *UnsupportedDiagnostic
}

type RouteSchemaMatrix struct {
	specs map[ProductArea]SurfaceSpec
}

func DefaultRouteSchemaMatrix() RouteSchemaMatrix {
	return RouteSchemaMatrix{
		specs: map[ProductArea]SurfaceSpec{
			ProductAreaIssues: {
				Area:     ProductAreaIssues,
				Status:   SupportStatusSupported,
				Route:    RouteFamilyAPIV5,
				Evidence: EvidenceClassOpenAPI,
			},
			ProductAreaLabels: {
				Area:     ProductAreaLabels,
				Status:   SupportStatusSupported,
				Route:    RouteFamilyAPIV5,
				Evidence: EvidenceClassOpenAPI,
			},
			ProductAreaMilestones: {
				Area:     ProductAreaMilestones,
				Status:   SupportStatusSupported,
				Route:    RouteFamilyAPIV5,
				Evidence: EvidenceClassOpenAPI,
			},
			ProductAreaPushMirrors: {
				Area:     ProductAreaPushMirrors,
				Status:   SupportStatusSupported,
				Route:    RouteFamilyAPIV5,
				Evidence: EvidenceClassOpenAPI,
			},
			ProductAreaWiki: {
				Area:     ProductAreaWiki,
				Status:   SupportStatusSupported,
				Route:    RouteFamilyAPIV5,
				Evidence: EvidenceClassLiveProbe,
			},
			ProductAreaPullRequests: {
				Area:     ProductAreaPullRequests,
				Status:   SupportStatusSupported,
				Route:    RouteFamilyAPIV5,
				Evidence: EvidenceClassOpenAPI,
			},
			ProductAreaComments: {
				Area:     ProductAreaComments,
				Status:   SupportStatusDeferred,
				Route:    RouteFamilyAPIV5,
				Evidence: EvidenceClassDeferred,
				Diagnostic: &UnsupportedDiagnostic{
					Code:          "unsupported_capability",
					CapabilityKey: "comments_read",
					Message:       "Comment reads are deferred for iteration 5 pending child-record shape validation",
				},
			},
		},
	}
}

func (m RouteSchemaMatrix) Spec(area ProductArea) (SurfaceSpec, bool) {
	spec, ok := m.specs[area]
	return spec, ok
}

func (m RouteSchemaMatrix) RequireDeclared(area ProductArea) error {
	_, ok := m.specs[area]
	if !ok {
		return fmt.Errorf("gitcode: product area %q is not declared in RouteSchemaMatrix", area)
	}
	return nil
}

func (m RouteSchemaMatrix) ValidateCoverage(required []ProductArea) error {
	for _, area := range required {
		if err := m.RequireDeclared(area); err != nil {
			return err
		}
		spec := m.specs[area]
		if err := validateSurfaceSpec(spec); err != nil {
			return fmt.Errorf("gitcode: invalid surface spec for %s: %w", area, err)
		}
	}
	return nil
}

func (m RouteSchemaMatrix) Preflight(area ProductArea) error {
	spec, ok := m.Spec(area)
	if !ok {
		return fmt.Errorf("gitcode: product area %q not declared in RouteSchemaMatrix", area)
	}
	if spec.Status == SupportStatusDeferred || spec.Status == SupportStatusUnsupported {
		diag := spec.Diagnostic
		if diag == nil {
			return ErrUnsupportedCapability{
				CapabilityKey: string(area) + "_read",
				Message:       fmt.Sprintf("product area %q is %s", area, spec.Status),
			}
		}
		return ErrUnsupportedCapability{
			CapabilityKey: diag.CapabilityKey,
			Message:       diag.Message,
		}
	}
	return nil
}

func (m RouteSchemaMatrix) DeclaredAreas() []ProductArea {
	areas := make([]ProductArea, 0, len(m.specs))
	for area := range m.specs {
		areas = append(areas, area)
	}
	sort.Slice(areas, func(i, j int) bool { return areas[i] < areas[j] })
	return areas
}

func validateSurfaceSpec(spec SurfaceSpec) error {
	if !validProductArea(spec.Area) {
		return fmt.Errorf("unknown ProductArea %q", spec.Area)
	}
	if !validSupportStatus(spec.Status) {
		return fmt.Errorf("unknown SupportStatus %q", spec.Status)
	}
	if !validEvidenceClass(spec.Evidence) {
		return fmt.Errorf("unknown EvidenceClass %q", spec.Evidence)
	}
	if spec.Route == "" {
		return fmt.Errorf("RouteFamily must not be empty")
	}
	if spec.Route != RouteFamilyAPIV5 {
		return fmt.Errorf("RouteFamily must be %q, got %q", RouteFamilyAPIV5, spec.Route)
	}
	if spec.Status == SupportStatusSupported && spec.Evidence == EvidenceClassDeferred {
		return fmt.Errorf("supported spec must not use deferred evidence")
	}
	if spec.Status == SupportStatusDeferred || spec.Status == SupportStatusUnsupported {
		if spec.Evidence != EvidenceClassDeferred {
			return fmt.Errorf("%s spec must use deferred evidence, got %q", spec.Status, spec.Evidence)
		}
		if spec.Diagnostic == nil {
			return fmt.Errorf("%s spec requires a diagnostic", spec.Status)
		}
		if spec.Diagnostic.Code != "unsupported_capability" {
			return fmt.Errorf("%s spec must have diagnostic code \"unsupported_capability\", got %q", spec.Status, spec.Diagnostic.Code)
		}
		if spec.Diagnostic.CapabilityKey == "" {
			return fmt.Errorf("%s spec diagnostic must have a non-empty capability key", spec.Status)
		}
		if spec.Diagnostic.Message == "" {
			return fmt.Errorf("%s spec diagnostic must have a non-empty message", spec.Status)
		}
	}
	if spec.Status == SupportStatusSupported && spec.Diagnostic != nil {
		return fmt.Errorf("supported spec must not have a diagnostic")
	}
	return nil
}