Files
PandeyandGitHub d9bcbece50 feat(v1alpha1): jsonschema validation for casting (#107)
## Summary
- Wires schema-based validation of `casting.yaml` at config load time.
Schema is generated from the Go types via `swaggest/jsonschema-go`,
embedded as `api/v1alpha1/schema.json`, and validated post-merge with
`google/jsonschema-go`.
- Adds field-level rules: `required`, `enum`, `pattern` (DNS-1123 name,
JSON Pointer paths, OCI image), `minimum`/`minLength`/`minItems` on the
relevant fields. `additionalProperties:false` is set on every nested
type for the published schema.
- Replaces the loose string `Platform` / `Mode` / `Flavor` on
`TypeDeployment` with typed enums following the existing `*Kind`
pattern. Bad values now fail at unmarshal with a clear "invalid
deployment …" message.
- Drops `InfrastructureProvider` in favor of `Platform` — the cloud
subset (aws, gcp, azure) drives the Terraform IaC pipeline directly.
`ResolveProvider` collapses `PlatformECS → PlatformAWS`.
- Side cleanups bundled here: typed errors in `internal/errors` with
stacktrace capture (used by yamlconfig to wrap validation failures as
`TypeInvalidInput`), and removal of the custom pretty slog handler in
`internal/instrumentation` in favor of `slog.NewJSONHandler`.

## Test plan
- [ ] `make gen-schemas` is idempotent (CI's `schemas` job stays green).
- [ ] `go test ./...` passes locally.
- [ ] Every committed example casting (`docs/examples/**/casting.yaml`,
`docs/standalone/casting.yaml`) loads without error.
- [ ] Negative cases at config load: bad `apiVersion`, missing required
fields, unknown enum values for `kind` / `mode` / `flavor` / `platform`
/ patch `op`, non-JSON-Pointer patch paths, negative `replicas`,
`shards: 0`, malformed image refs — each fails with a `TypeInvalidInput`
error pointing at the offending location.
- [ ] `foundryctl forge` against
`docs/examples/docker/compose/casting.yaml` still produces the expected
pours.
2026-05-01 09:04:53 +05:30

102 lines
2.1 KiB
Go

package v1alpha1
import (
"encoding/json"
"errors"
"fmt"
"github.com/swaggest/jsonschema-go"
"go.yaml.in/yaml/v3"
)
var _ yaml.Marshaler = (*Flavor)(nil)
var _ yaml.Unmarshaler = (*Flavor)(nil)
var _ json.Marshaler = (*Flavor)(nil)
var _ json.Unmarshaler = (*Flavor)(nil)
var _ fmt.Stringer = (*Flavor)(nil)
var _ jsonschema.Enum = (*Flavor)(nil)
var (
FlavorCompose Flavor = Flavor{s: "compose"}
FlavorSwarm Flavor = Flavor{s: "swarm"}
FlavorBinary Flavor = Flavor{s: "binary"}
FlavorKustomize Flavor = Flavor{s: "kustomize"}
FlavorHelm Flavor = Flavor{s: "helm"}
FlavorBlueprint Flavor = Flavor{s: "blueprint"}
FlavorStack Flavor = Flavor{s: "stack"}
FlavorTemplate Flavor = Flavor{s: "template"}
FlavorTerraform Flavor = Flavor{s: "terraform"}
)
type Flavor struct {
s string
}
func (flavor Flavor) String() string {
return flavor.s
}
func Flavors() []Flavor {
return []Flavor{
FlavorCompose,
FlavorSwarm,
FlavorBinary,
FlavorKustomize,
FlavorHelm,
FlavorBlueprint,
FlavorStack,
FlavorTemplate,
FlavorTerraform,
}
}
func (flavor Flavor) MarshalJSON() ([]byte, error) {
return json.Marshal(flavor.String())
}
func (flavor *Flavor) UnmarshalJSON(text []byte) error {
var str string
if err := json.Unmarshal(text, &str); err != nil {
return err
}
return flavor.UnmarshalText([]byte(str))
}
func (flavor *Flavor) UnmarshalText(text []byte) error {
for _, available := range Flavors() {
if available.String() == string(text) {
*flavor = available
return nil
}
}
if len(text) == 0 {
*flavor = Flavor{s: ""}
return nil
}
return errors.New("invalid deployment flavor: " + string(text))
}
func (flavor Flavor) MarshalText() ([]byte, error) {
return []byte(flavor.String()), nil
}
func (flavor *Flavor) UnmarshalYAML(node *yaml.Node) error {
return flavor.UnmarshalText([]byte(node.Value))
}
func (flavor Flavor) MarshalYAML() (any, error) {
return flavor.String(), nil
}
func (flavor Flavor) Enum() []any {
flavors := []any{}
for _, flavor := range Flavors() {
flavors = append(flavors, flavor.String())
}
return flavors
}