Files
spinifex/internal/awsmodel/coverage.go
T
Julian Sommer b89334d76f docs: keep the not-applicable operations out of the internal gap list
The internal report listed every unimplemented operation together, so the 87
EC2 operations declared not applicable sat in the same list as the work, where
anything reading it for candidates would pick them up.

They are now listed apart, grouped under the reason they were declared with,
and the counts line reports them as not applicable rather than as gaps.
2026-09-15 11:04:35 +10:00

236 lines
8.3 KiB
Go

package awsmodel
import (
"fmt"
"sort"
"strings"
)
// DispatchInventory is the gateway-side operation registration state supplied
// to CompareOperations.
type DispatchInventory struct {
Registered []string
Stubbed []string
Unsupported []string
Opaque bool
Note string
}
// OperationCoverage is a deterministic model-versus-dispatch comparison.
// Implemented contains only modelled operations with a real handler; Extra
// contains registered operations absent from the pinned model.
type OperationCoverage struct {
Service Service
APIVersion string
Modelled []string
Registered []string
Implemented []string
Stubbed []string
Unsupported []string
Missing []string
Extra []string
Opaque bool
Note string
}
// CompareOperations compares one embedded service model with an authoritative
// gateway dispatch inventory.
func CompareOperations(service Service, dispatch DispatchInventory) (OperationCoverage, error) {
model, err := Load(service)
if err != nil {
return OperationCoverage{}, err
}
coverage := OperationCoverage{
Service: service,
APIVersion: model.Metadata().APIVersion,
Modelled: model.Operations(),
Opaque: dispatch.Opaque,
Note: dispatch.Note,
}
if dispatch.Opaque {
return coverage, nil
}
registered, err := uniqueSorted("registered", dispatch.Registered)
if err != nil {
return OperationCoverage{}, fmt.Errorf("awsmodel: %s dispatch: %w", service, err)
}
stubbed, err := uniqueSorted("stubbed", dispatch.Stubbed)
if err != nil {
return OperationCoverage{}, fmt.Errorf("awsmodel: %s dispatch: %w", service, err)
}
unsupported, err := uniqueSorted("unsupported", dispatch.Unsupported)
if err != nil {
return OperationCoverage{}, fmt.Errorf("awsmodel: %s dispatch: %w", service, err)
}
registeredSet := toSet(registered)
stubbedSet := toSet(stubbed)
unsupportedSet := toSet(unsupported)
for operation := range stubbedSet {
if !registeredSet[operation] {
return OperationCoverage{}, fmt.Errorf("awsmodel: %s stubbed operation %q is not registered", service, operation)
}
if unsupportedSet[operation] {
return OperationCoverage{}, fmt.Errorf("awsmodel: %s operation %q is both stubbed and unsupported", service, operation)
}
}
for operation := range unsupportedSet {
if !registeredSet[operation] {
return OperationCoverage{}, fmt.Errorf("awsmodel: %s unsupported operation %q is not registered", service, operation)
}
}
modelledSet := toSet(coverage.Modelled)
for _, operation := range coverage.Modelled {
if !registeredSet[operation] {
coverage.Missing = append(coverage.Missing, operation)
continue
}
if !stubbedSet[operation] && !unsupportedSet[operation] {
coverage.Implemented = append(coverage.Implemented, operation)
}
}
for _, operation := range registered {
if !modelledSet[operation] {
coverage.Extra = append(coverage.Extra, operation)
}
}
coverage.Registered = registered
coverage.Stubbed = stubbed
coverage.Unsupported = unsupported
return coverage, nil
}
func uniqueSorted(label string, values []string) ([]string, error) {
result := append([]string(nil), values...)
sort.Strings(result)
for index, value := range result {
if value == "" {
return nil, fmt.Errorf("%s operation is empty", label)
}
if index > 0 && result[index-1] == value {
return nil, fmt.Errorf("%s operation %q is duplicated", label, value)
}
}
return result, nil
}
func toSet(values []string) map[string]bool {
set := make(map[string]bool, len(values))
for _, value := range values {
set[value] = true
}
return set
}
// RenderCoverageSummary renders the cross-service counts as plain text for a
// terminal. The published pages carry the per-operation detail.
func RenderCoverageSummary(coverages []OperationCoverage) string {
var report strings.Builder
fmt.Fprintf(&report, "AWS model operation coverage (aws-sdk-go %s)\n\n", SourceSDKVersion)
for _, coverage := range sortedCoverages(coverages) {
if coverage.Opaque {
fmt.Fprintf(&report, "%-24s %-12s not enumerable\n", coverage.Service, coverage.APIVersion)
continue
}
fmt.Fprintf(&report, "%-24s %-12s %3d of %3d implemented (%5.1f%%), %3d stubbed, %3d unsupported, %3d outside the model\n",
coverage.Service, coverage.APIVersion, len(coverage.Implemented), len(coverage.Modelled),
coverage.ImplementedPercent(), len(coverage.Stubbed), len(coverage.Unsupported), len(coverage.Extra))
}
return report.String()
}
// RenderInternalReport renders the full comparison, including the operations
// the published pages leave out. It is not a site page: the docs build renders
// only the slugs it is configured with, and this file is not one of them.
//
// A declared not-applicable operation is listed apart from the gaps, with the
// reason, so a list of candidate work never carries one nobody intends to
// build.
func RenderInternalReport(coverages []OperationCoverage, pages PageSet) string {
var report strings.Builder
report.WriteString("# AWS Model Operation Coverage (internal)\n\n")
report.WriteString("Generated by `make aws-model-coverage`. This file is not published: it carries the counts and the unimplemented operations that the pages under this directory deliberately leave out.\n\n")
report.WriteString("**Not implemented** is the candidate work. **Not applicable** is declared in `pages.json` and is not work: those operations describe something this platform will not have.\n\n")
fmt.Fprintf(&report, "Pinned model source: `aws-sdk-go %s`.\n\n", SourceSDKVersion)
for _, coverage := range sortedCoverages(coverages) {
page := pages.Services[coverage.Service]
fmt.Fprintf(&report, "## %s\n\n", coverage.Service)
if coverage.Opaque {
fmt.Fprintf(&report, "`%s` — not enumerable. %s\n\n", coverage.APIVersion, coverage.Note)
continue
}
missing, notApplicable := splitNotApplicable(coverage.Missing, page)
refused, declaredRefusals := splitNotApplicable(coverage.Unsupported, page)
notApplicable = append(notApplicable, declaredRefusals...)
sort.Strings(notApplicable)
fmt.Fprintf(&report, "`%s` — %d of %d modelled operations implemented (%.1f%%), %d stubbed, %d not applicable, %d registered outside the pinned model.\n\n",
coverage.APIVersion, len(coverage.Implemented), len(coverage.Modelled), coverage.ImplementedPercent(),
len(coverage.Stubbed), len(notApplicable), len(coverage.Extra))
writeOperationList(&report, "Not implemented", missing)
writeOperationList(&report, "Stubbed", coverage.Stubbed)
writeOperationList(&report, "Registered and refused", refused)
writeOperationList(&report, "Registered outside the pinned model", coverage.Extra)
writeNotApplicableList(&report, notApplicable, page)
}
return report.String()
}
// splitNotApplicable divides operations into those still open and those the
// page declares the platform will never serve.
func splitNotApplicable(operations []string, page PageMetadata) (open, notApplicable []string) {
for _, operation := range operations {
if _, declared := page.NotApplicable[operation]; declared {
notApplicable = append(notApplicable, operation)
continue
}
open = append(open, operation)
}
return open, notApplicable
}
// writeNotApplicableList records why each declared operation is not work,
// grouped by the reason it was declared under.
func writeNotApplicableList(report *strings.Builder, operations []string, page PageMetadata) {
if len(operations) == 0 {
return
}
byNote := map[string][]string{}
for _, operation := range operations {
key := page.NotApplicable[operation]
byNote[key] = append(byNote[key], operation)
}
report.WriteString("### Not applicable — do not build\n\n")
for _, key := range noteOrder(page.Notes) {
if len(byNote[key]) == 0 {
continue
}
fmt.Fprintf(report, "%s: %s\n\n", key, page.Notes[key])
for _, operation := range byNote[key] {
fmt.Fprintf(report, "- `%s`\n", operation)
}
report.WriteString("\n")
}
}
func writeOperationList(report *strings.Builder, heading string, operations []string) {
if len(operations) == 0 {
return
}
fmt.Fprintf(report, "### %s\n\n", heading)
for _, operation := range operations {
fmt.Fprintf(report, "- `%s`\n", operation)
}
report.WriteString("\n")
}
func sortedCoverages(coverages []OperationCoverage) []OperationCoverage {
ordered := append([]OperationCoverage(nil), coverages...)
sort.Slice(ordered, func(i, j int) bool { return ordered[i].Service < ordered[j].Service })
return ordered
}