package bundle import ( "bytes" "encoding/json" "fmt" "io" "time" ) const ManifestName = "manifest.json" type Manifest struct { SchemaVersion int `json:"schema_version"` ID string `json:"id"` Digest string `json:"digest"` Created time.Time `json:"created"` Files []ManifestFile `json:"files"` } type ManifestFile struct { Path string `json:"path"` SHA256 string `json:"sha256"` Size int64 `json:"size"` } type Bundle struct { RootRelativePath string Manifest Manifest } type rawManifest struct { SchemaVersion *int `json:"schema_version"` ID *string `json:"id"` Digest *string `json:"digest"` Created *string `json:"created"` Files []rawManifestFile `json:"files"` } type rawManifestFile struct { Path *string `json:"path"` SHA256 *string `json:"sha256"` Size *int64 `json:"size"` } func ParseManifest(data []byte) (Manifest, error) { decoder := json.NewDecoder(bytes.NewReader(data)) var raw rawManifest if err := decoder.Decode(&raw); err != nil { return Manifest{}, fmt.Errorf("parse manifest: %w", err) } var extra any if err := decoder.Decode(&extra); err != io.EOF { return Manifest{}, fmt.Errorf("parse manifest: trailing data") } var manifest Manifest if raw.SchemaVersion == nil { return Manifest{}, fmt.Errorf("manifest schema_version is required") } manifest.SchemaVersion = *raw.SchemaVersion if raw.ID == nil || *raw.ID == "" { return Manifest{}, fmt.Errorf("manifest id is required") } manifest.ID = *raw.ID if raw.Digest == nil || *raw.Digest == "" { return Manifest{}, fmt.Errorf("manifest digest is required") } manifest.Digest = *raw.Digest if raw.Created == nil || *raw.Created == "" { return Manifest{}, fmt.Errorf("manifest created is required") } created, err := time.Parse(time.RFC3339, *raw.Created) if err != nil { return Manifest{}, fmt.Errorf("manifest created must be RFC3339: %w", err) } manifest.Created = created if len(raw.Files) == 0 { return Manifest{}, fmt.Errorf("manifest files is required") } for index, rawFile := range raw.Files { file, err := parseManifestFile(index, rawFile) if err != nil { return Manifest{}, err } manifest.Files = append(manifest.Files, file) } if err := ValidateManifest(manifest); err != nil { return Manifest{}, fmt.Errorf("manifest %w", err) } return manifest, nil } func parseManifestFile(index int, raw rawManifestFile) (ManifestFile, error) { if raw.Path == nil || *raw.Path == "" { return ManifestFile{}, fmt.Errorf("manifest files[%d].path is required", index) } if raw.SHA256 == nil || *raw.SHA256 == "" { return ManifestFile{}, fmt.Errorf("manifest files[%d].sha256 is required", index) } if raw.Size == nil { return ManifestFile{}, fmt.Errorf("manifest files[%d].size is required", index) } return ManifestFile{ Path: *raw.Path, SHA256: *raw.SHA256, Size: *raw.Size, }, nil }