Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions changes/unreleased/Added-20260905-171000.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Added
body: 'All 48 upstream AI/Machine Learning service models are registered, taking DevCloud from 105 to 148 services. 17 are served by the generic CRUD engine or a hand-written provider; the remaining 31 are routed and decline with a clean AWS error rather than letting the call fall through to real AWS. See docs/coverage.md for the registered / served split'
time: 2026-09-05T17:10:00.000000+09:00
custom:
Issue: "137"
5 changes: 5 additions & 0 deletions changes/unreleased/Added-20260905-171001.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Added
body: 'docs/coverage.md, which publishes the registered, serving and registered-only service counts together. The service count is never stated on its own'
time: 2026-09-05T17:10:01.000000+09:00
custom:
Issue: "137"
5 changes: 5 additions & 0 deletions changes/unreleased/Changed-20260905-171002.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Changed
body: 'Service routing aliases are derived from the Smithy models instead of a hand-maintained switch in the gateway. Onboarding a service no longer needs a hand-written routing entry: all 93 previously-routed aliases resolve unchanged, and an alias claimed by more than one service is reported rather than guessed'
time: 2026-09-05T17:10:02.000000+09:00
custom:
Issue: "137"
5 changes: 5 additions & 0 deletions changes/unreleased/Changed-20260905-171003.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Changed
body: 'The fidelity manifest records each service''s wire protocol, so a registered service that the CRUD engine cannot reach is distinguishable from one whose wiring is broken'
time: 2026-09-05T17:10:03.000000+09:00
custom:
Issue: "137"
5 changes: 5 additions & 0 deletions changes/unreleased/Fixed-20260905-171004.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Fixed
body: 'Smithy''s non-boxed primitives (PrimitiveLong, PrimitiveBoolean and the other five) are mapped to Go types. Models using them previously generated code that did not compile'
time: 2026-09-05T17:10:04.000000+09:00
custom:
Issue: "137"
5 changes: 5 additions & 0 deletions changes/unreleased/Fixed-20260905-171005.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Fixed
body: '''make codegen'' is now byte-identical across runs. A model bundling two namespaces with colliding shape names produced different output on every run, because shapes are keyed by short name and the generator iterated a Go map'
time: 2026-09-05T17:10:05.000000+09:00
custom:
Issue: "137"
5 changes: 5 additions & 0 deletions changes/unreleased/Fixed-20260905-171006.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
kind: Fixed
body: 'Amazon Rekognition can be onboarded. Its X-Amz-Target prefix is RekognitionService, which no routing entry covered, so every call returned UnknownService'
time: 2026-09-05T17:10:06.000000+09:00
custom:
Issue: "137"
44 changes: 42 additions & 2 deletions cmd/codegen/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"strings"

"github.com/skyoo2003/devcloud/internal/codegen"
"github.com/skyoo2003/devcloud/internal/codegen/ir"
)

// handWritten services get no generated package: their provider is implemented
Expand Down Expand Up @@ -46,7 +47,9 @@ func main() {
gen := codegen.NewGenerator(*templateDir)

var crudServices []codegen.CRUDServiceData
var allModels []*ir.Model
modelOps := make(map[string][]string)
protocols := make(map[string]string)
// A model that cannot be read or parsed is skipped, which used to leave the
// exit status at 0 — so a drift check downstream saw no changed files and
// called incomplete generation clean.
Expand Down Expand Up @@ -92,6 +95,12 @@ func main() {
names = append(names, op.Name)
}
modelOps[model.ServiceID] = names
protocols[model.ServiceID] = model.Protocol

// Also recorded before the filters: a hand-written provider still has to
// be routed to, so its model still contributes routing aliases even
// though it generates no package.
allModels = append(allModels, model)

if len(allowedServices) > 0 && !allowedServices[model.ServiceID] {
continue
Expand Down Expand Up @@ -149,27 +158,58 @@ func main() {
os.Exit(1)
}

if err := writeFidelityManifest(gen, *outputDir, modelOps, providers, crudServices); err != nil {
if err := writeFidelityManifest(gen, *outputDir, modelOps, protocols, providers, crudServices); err != nil {
fmt.Fprintf(os.Stderr, "Error writing fidelity manifest: %v\n", err)
os.Exit(1)
}

if err := writeAliases(gen, *outputDir, allModels); err != nil {
fmt.Fprintf(os.Stderr, "Error writing alias table: %v\n", err)
os.Exit(1)
}
}

fmt.Println("Code generation complete.")
}

// writeAliases emits the routing alias table. Contested aliases are reported on
// stderr rather than being resolved here: the generator cannot see a request, so
// it cannot tell an opensearch call from an elasticsearchservice one. They are
// named in the generated Collisions list, and a gateway test fails until each has
// a resolution — loud, but not fatal to generation, because a collision blocks
// one alias rather than making the whole fleet's generated output wrong.
func writeAliases(gen *codegen.Generator, outputDir string, models []*ir.Model) error {
table, collisions := codegen.BuildAliases(models)
for _, alias := range collisions {
fmt.Fprintf(os.Stderr, "WARN: alias %q is claimed by more than one service; "+
"it is omitted from the table and needs a resolution in the gateway\n", alias)
}

content, err := gen.GenerateAliases(table, collisions)
if err != nil {
return err
}

dir := filepath.Join(outputDir, "aliases")
if err := os.MkdirAll(dir, 0755); err != nil {
return err
}
return codegen.WriteGo(filepath.Join(dir, "aliases_gen.go"), content)
}

// writeFidelityManifest emits the per-operation fidelity manifest. Like the CRUD
// registry it is only written on a full-fleet run, since a filtered run would
// produce a partial manifest.
func writeFidelityManifest(
gen *codegen.Generator,
outputDir string,
modelOps map[string][]string,
protocols map[string]string,
providers map[string]codegen.ProviderScan,
crudServices []codegen.CRUDServiceData,
) error {
content, err := gen.GenerateFidelityManifest(
codegen.BuildFidelityData(modelOps, providers, crudServices),
codegen.BuildFidelityData(modelOps, protocols, providers, crudServices),
)
if err != nil {
return err
Expand Down
35 changes: 25 additions & 10 deletions cmd/devcloud/fidelity_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,32 @@ func TestFidelityManifestCoverage(t *testing.T) {
served++
}
}
// Every routed service must serve something. Hand-verified operations and
// engine-served ones both count: a scaffolded service legitimately
// implements nothing by hand and is served entirely by the CRUD engine.
// Every routed service must serve something — unless its protocol puts
// it out of the CRUD engine's reach and nobody has written it by hand.
//
// Zero of both is the real defect — a service registered and routed that
// answers nothing. That happens when the dispatch scan loses a provider
// (a new path-routing provider needs a pathRoutedOps entry in
// internal/codegen/scan_handverified.go) or when a provider declines
// without returning plugin.ErrUnhandledOp, so it never reaches the engine.
if served == 0 {
t.Errorf("%s: serves no operations — neither hand-verified nor engine-served", id)
// The engine classifies from the operation name, which only the
// X-Amz-Target JSON protocols carry (crud.JSONProtocol). A restJson1,
// query or restXml service therefore serves nothing until it gets a
// hand-written provider. Registering it anyway is deliberate: it routes,
// and it declines with a clean AWS error instead of the caller falling
// through to real AWS. What it must never do is count as covered — see
// docs/coverage.md, which publishes the registered / engine-served split
// rather than the registered number alone.
//
// The defect this leaves is the sharp one: the engine holds classified
// operations for the service, and the manifest still says none is
// served. That is wiring, not protocol — the dispatch scan lost a
// provider (a new path-routing provider needs a pathRoutedOps entry in
// internal/codegen/scan_handverified.go), or a provider declines without
// returning plugin.ErrUnhandledOp and so never reaches the engine.
//
// An engine-servable service with nothing in the registry is a third,
// legitimate case: forecastquery is json-1.1 but its whole API is
// QueryForecast / QueryWhatIfForecast, and neither is CRUD-shaped. There
// is nothing to serve generically, so zero served is the truth.
if served == 0 && len(crud.RegisteredOps(id)) > 0 {
t.Errorf("%s: the CRUD engine holds %d operations for it, but the manifest serves none",
id, len(crud.RegisteredOps(id)))
}
}
if operations < minOperations {
Expand Down
43 changes: 43 additions & 0 deletions cmd/devcloud/imports.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading