ADR 0627 §1: the four registry seats' clients publish a file in parts over their own seat
Each client binds mesh-sdk's seat-neutral parts sender to its own seat's publish and publish-part and types, so no client uses another seat's; each is a Go module and a package artifact of its own (ADR 0625 §3). Claude-Session: https://claude.ai/code/session_01JG73KJNrH5EgFXMKKYSrig
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
// Package containerimageregistryclient is the container-image-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51
|
||||
// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part,
|
||||
// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a
|
||||
// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is
|
||||
// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the
|
||||
// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport.
|
||||
package containerimageregistryclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// Call asks one verb of the seat, `seat:<seat>.<verb>`, with a body, and answers its JSON.
|
||||
type Call func(key string, body any) (json.RawMessage, error)
|
||||
|
||||
// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were
|
||||
// sent, and the chunks they took.
|
||||
type Sent struct {
|
||||
Transfer string
|
||||
parts.Sent
|
||||
}
|
||||
|
||||
// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in
|
||||
// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes
|
||||
// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read.
|
||||
// Its outcome, published or refused, is the holder's event, which the caller waits for.
|
||||
func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) {
|
||||
open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) {
|
||||
in.Parts = make([]seat.Part, 0, len(ps))
|
||||
for _, p := range ps {
|
||||
in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size})
|
||||
}
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in)
|
||||
if err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err)
|
||||
}
|
||||
var a seat.PublishAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err)
|
||||
}
|
||||
return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil
|
||||
}
|
||||
chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error {
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part,
|
||||
Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s's publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
var a seat.PublishPartAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
sent, err := parts.Send(ctx, open, chunk, ps)
|
||||
return Sent{Transfer: in.Transfer, Sent: sent}, err
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package containerimageregistryclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the
|
||||
// holder lacks travels by publish-part, its bytes whole, the last chunk marked.
|
||||
func TestPublishTalksToItsOwnSeat(t *testing.T) {
|
||||
held, lacked := []byte("held"), []byte("lacked")
|
||||
part := func(b []byte) parts.Part {
|
||||
sum := sha256.Sum256(b)
|
||||
return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)),
|
||||
Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }}
|
||||
}
|
||||
ps := []parts.Part{part(held), part(lacked)}
|
||||
var asked []string
|
||||
var got []byte
|
||||
call := func(key string, body any) (json.RawMessage, error) {
|
||||
asked = append(asked, key)
|
||||
raw, _ := json.Marshal(body)
|
||||
switch key {
|
||||
case "seat:container-image-registry.publish":
|
||||
var in seat.PublishInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
if len(in.Parts) != 2 {
|
||||
t.Errorf("publish names %d parts, want 2", len(in.Parts))
|
||||
}
|
||||
return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}})
|
||||
case "seat:container-image-registry.publish-part":
|
||||
var in seat.PublishPartInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
data, _ := base64.StdEncoding.DecodeString(in.Data)
|
||||
if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" {
|
||||
t.Errorf("publish-part %+v", in)
|
||||
}
|
||||
got = append(got, data...)
|
||||
return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part})
|
||||
}
|
||||
t.Fatalf("asked %s", key)
|
||||
return nil, nil
|
||||
}
|
||||
sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps)
|
||||
if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 {
|
||||
t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module git.novox.be/novox/mesh-contracts/go/clients/containerimageregistryclient
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/containerimageregistry v0.0.0
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
// Package gopackageregistryclient is the go-package-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51
|
||||
// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part,
|
||||
// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a
|
||||
// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is
|
||||
// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the
|
||||
// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport.
|
||||
package gopackageregistryclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// Call asks one verb of the seat, `seat:<seat>.<verb>`, with a body, and answers its JSON.
|
||||
type Call func(key string, body any) (json.RawMessage, error)
|
||||
|
||||
// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were
|
||||
// sent, and the chunks they took.
|
||||
type Sent struct {
|
||||
Transfer string
|
||||
parts.Sent
|
||||
}
|
||||
|
||||
// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in
|
||||
// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes
|
||||
// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read.
|
||||
// Its outcome, published or refused, is the holder's event, which the caller waits for.
|
||||
func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) {
|
||||
open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) {
|
||||
in.Parts = make([]seat.Part, 0, len(ps))
|
||||
for _, p := range ps {
|
||||
in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size})
|
||||
}
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in)
|
||||
if err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err)
|
||||
}
|
||||
var a seat.PublishAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err)
|
||||
}
|
||||
return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil
|
||||
}
|
||||
chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error {
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part,
|
||||
Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s's publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
var a seat.PublishPartAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
sent, err := parts.Send(ctx, open, chunk, ps)
|
||||
return Sent{Transfer: in.Transfer, Sent: sent}, err
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package gopackageregistryclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the
|
||||
// holder lacks travels by publish-part, its bytes whole, the last chunk marked.
|
||||
func TestPublishTalksToItsOwnSeat(t *testing.T) {
|
||||
held, lacked := []byte("held"), []byte("lacked")
|
||||
part := func(b []byte) parts.Part {
|
||||
sum := sha256.Sum256(b)
|
||||
return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)),
|
||||
Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }}
|
||||
}
|
||||
ps := []parts.Part{part(held), part(lacked)}
|
||||
var asked []string
|
||||
var got []byte
|
||||
call := func(key string, body any) (json.RawMessage, error) {
|
||||
asked = append(asked, key)
|
||||
raw, _ := json.Marshal(body)
|
||||
switch key {
|
||||
case "seat:go-package-registry.publish":
|
||||
var in seat.PublishInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
if len(in.Parts) != 2 {
|
||||
t.Errorf("publish names %d parts, want 2", len(in.Parts))
|
||||
}
|
||||
return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}})
|
||||
case "seat:go-package-registry.publish-part":
|
||||
var in seat.PublishPartInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
data, _ := base64.StdEncoding.DecodeString(in.Data)
|
||||
if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" {
|
||||
t.Errorf("publish-part %+v", in)
|
||||
}
|
||||
got = append(got, data...)
|
||||
return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part})
|
||||
}
|
||||
t.Fatalf("asked %s", key)
|
||||
return nil, nil
|
||||
}
|
||||
sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps)
|
||||
if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 {
|
||||
t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module git.novox.be/novox/mesh-contracts/go/clients/gopackageregistryclient
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/gopackageregistry v0.0.0
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
// Package npmpackageregistryclient is the npm-package-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51
|
||||
// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part,
|
||||
// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a
|
||||
// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is
|
||||
// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the
|
||||
// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport.
|
||||
package npmpackageregistryclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// Call asks one verb of the seat, `seat:<seat>.<verb>`, with a body, and answers its JSON.
|
||||
type Call func(key string, body any) (json.RawMessage, error)
|
||||
|
||||
// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were
|
||||
// sent, and the chunks they took.
|
||||
type Sent struct {
|
||||
Transfer string
|
||||
parts.Sent
|
||||
}
|
||||
|
||||
// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in
|
||||
// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes
|
||||
// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read.
|
||||
// Its outcome, published or refused, is the holder's event, which the caller waits for.
|
||||
func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) {
|
||||
open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) {
|
||||
in.Parts = make([]seat.Part, 0, len(ps))
|
||||
for _, p := range ps {
|
||||
in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size})
|
||||
}
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in)
|
||||
if err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err)
|
||||
}
|
||||
var a seat.PublishAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err)
|
||||
}
|
||||
return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil
|
||||
}
|
||||
chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error {
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part,
|
||||
Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s's publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
var a seat.PublishPartAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
sent, err := parts.Send(ctx, open, chunk, ps)
|
||||
return Sent{Transfer: in.Transfer, Sent: sent}, err
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package npmpackageregistryclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the
|
||||
// holder lacks travels by publish-part, its bytes whole, the last chunk marked.
|
||||
func TestPublishTalksToItsOwnSeat(t *testing.T) {
|
||||
held, lacked := []byte("held"), []byte("lacked")
|
||||
part := func(b []byte) parts.Part {
|
||||
sum := sha256.Sum256(b)
|
||||
return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)),
|
||||
Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }}
|
||||
}
|
||||
ps := []parts.Part{part(held), part(lacked)}
|
||||
var asked []string
|
||||
var got []byte
|
||||
call := func(key string, body any) (json.RawMessage, error) {
|
||||
asked = append(asked, key)
|
||||
raw, _ := json.Marshal(body)
|
||||
switch key {
|
||||
case "seat:npm-package-registry.publish":
|
||||
var in seat.PublishInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
if len(in.Parts) != 2 {
|
||||
t.Errorf("publish names %d parts, want 2", len(in.Parts))
|
||||
}
|
||||
return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}})
|
||||
case "seat:npm-package-registry.publish-part":
|
||||
var in seat.PublishPartInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
data, _ := base64.StdEncoding.DecodeString(in.Data)
|
||||
if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" {
|
||||
t.Errorf("publish-part %+v", in)
|
||||
}
|
||||
got = append(got, data...)
|
||||
return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part})
|
||||
}
|
||||
t.Fatalf("asked %s", key)
|
||||
return nil, nil
|
||||
}
|
||||
sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps)
|
||||
if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 {
|
||||
t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module git.novox.be/novox/mesh-contracts/go/clients/npmpackageregistryclient
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/npmpackageregistry v0.0.0
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0
|
||||
)
|
||||
@@ -0,0 +1,63 @@
|
||||
// Package ociartifactregistryclient is the oci-artifact-registry seat's client (novox/hq ADR 0627 §1, §2; ADR 0611, design 51
|
||||
// *Publishing*): it publishes a file in parts to the seat's holder. `publish` opens the transfer naming every part,
|
||||
// the holder answers the parts it lacks, and only those travel by `publish-part`, in chunks a bus message carries; a
|
||||
// cut transfer goes on from what the holder answers when the same transfer is opened again. The sending in parts is
|
||||
// mesh-sdk's seat-neutral runtime (parts); this client binds it to this seat's verbs and types alone. The call is the
|
||||
// caller's own (a bundle's stdio.Ask, a process's call of a seat verb), so it holds no transport.
|
||||
package ociartifactregistryclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/ociartifactregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// Call asks one verb of the seat, `seat:<seat>.<verb>`, with a body, and answers its JSON.
|
||||
type Call func(key string, body any) (json.RawMessage, error)
|
||||
|
||||
// Sent is what a Publish did: the transfer, whether the holder held its digest already, the parts it lacked and were
|
||||
// sent, and the chunks they took.
|
||||
type Sent struct {
|
||||
Transfer string
|
||||
parts.Sent
|
||||
}
|
||||
|
||||
// Publish opens (or opens again) the transfer in on the seat and sends the parts the holder answers it lacks, each in
|
||||
// chunks of at most parts.MaxChunk, in sequence, the last marked. in.Parts is written from parts. A part whose bytes
|
||||
// do not hash to its digest is refused before anything of it is sent; a part the holder does not name is never read.
|
||||
// Its outcome, published or refused, is the holder's event, which the caller waits for.
|
||||
func Publish(ctx context.Context, call Call, in seat.PublishInput, ps []parts.Part) (Sent, error) {
|
||||
open := func(_ context.Context, ps []parts.Part) (parts.Opened, error) {
|
||||
in.Parts = make([]seat.Part, 0, len(ps))
|
||||
for _, p := range ps {
|
||||
in.Parts = append(in.Parts, seat.Part{Digest: p.Digest, Size: p.Size})
|
||||
}
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublish, in)
|
||||
if err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's publish of %s: %w", seat.Seat, in.Name, err)
|
||||
}
|
||||
var a seat.PublishAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return parts.Opened{}, fmt.Errorf("%s's answer to publish: %w", seat.Seat, err)
|
||||
}
|
||||
return parts.Opened{Held: a.Held, Lacks: a.Lacks}, nil
|
||||
}
|
||||
chunk := func(_ context.Context, part string, sequence int, last bool, data []byte) error {
|
||||
raw, err := call("seat:"+seat.Seat+"."+seat.VerbPublishPart, seat.PublishPartInput{Transfer: in.Transfer, Part: part,
|
||||
Sequence: int64(sequence), Last: last, Data: base64.StdEncoding.EncodeToString(data)})
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s's publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
var a seat.PublishPartAnswer
|
||||
if err := json.Unmarshal(raw, &a); err != nil {
|
||||
return fmt.Errorf("%s's answer to publish-part: %w", seat.Seat, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
sent, err := parts.Send(ctx, open, chunk, ps)
|
||||
return Sent{Transfer: in.Transfer, Sent: sent}, err
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package ociartifactregistryclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"crypto/sha256"
|
||||
"encoding/base64"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"testing"
|
||||
|
||||
seat "git.novox.be/novox/mesh-contracts/go/seats/ociartifactregistry"
|
||||
"git.novox.be/novox/mesh-sdk/go/parts"
|
||||
)
|
||||
|
||||
// The client publishes over its own seat's verbs alone (ADR 0627 §1): publish names every part, and only the part the
|
||||
// holder lacks travels by publish-part, its bytes whole, the last chunk marked.
|
||||
func TestPublishTalksToItsOwnSeat(t *testing.T) {
|
||||
held, lacked := []byte("held"), []byte("lacked")
|
||||
part := func(b []byte) parts.Part {
|
||||
sum := sha256.Sum256(b)
|
||||
return parts.Part{Digest: "sha256:" + hex.EncodeToString(sum[:]), Size: int64(len(b)),
|
||||
Open: func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(b)), nil }}
|
||||
}
|
||||
ps := []parts.Part{part(held), part(lacked)}
|
||||
var asked []string
|
||||
var got []byte
|
||||
call := func(key string, body any) (json.RawMessage, error) {
|
||||
asked = append(asked, key)
|
||||
raw, _ := json.Marshal(body)
|
||||
switch key {
|
||||
case "seat:oci-artifact-registry.publish":
|
||||
var in seat.PublishInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
if len(in.Parts) != 2 {
|
||||
t.Errorf("publish names %d parts, want 2", len(in.Parts))
|
||||
}
|
||||
return json.Marshal(seat.PublishAnswer{Transfer: in.Transfer, Lacks: []string{ps[1].Digest}})
|
||||
case "seat:oci-artifact-registry.publish-part":
|
||||
var in seat.PublishPartInput
|
||||
_ = json.Unmarshal(raw, &in)
|
||||
data, _ := base64.StdEncoding.DecodeString(in.Data)
|
||||
if in.Part != ps[1].Digest || !in.Last || in.Transfer != "t1" {
|
||||
t.Errorf("publish-part %+v", in)
|
||||
}
|
||||
got = append(got, data...)
|
||||
return json.Marshal(seat.PublishPartAnswer{Transfer: in.Transfer, Part: in.Part})
|
||||
}
|
||||
t.Fatalf("asked %s", key)
|
||||
return nil, nil
|
||||
}
|
||||
sent, err := Publish(context.Background(), call, seat.PublishInput{Transfer: "t1", Name: "x"}, ps)
|
||||
if err != nil || sent.Transfer != "t1" || sent.Chunks != 1 || len(sent.Parts) != 1 || !bytes.Equal(got, lacked) || len(asked) != 2 {
|
||||
t.Errorf("sent %+v, %q arrived, asked %v, %v", sent, got, asked, err)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
module git.novox.be/novox/mesh-contracts/go/clients/ociartifactregistryclient
|
||||
|
||||
go 1.26.0
|
||||
|
||||
require (
|
||||
git.novox.be/novox/mesh-contracts/go/seats/ociartifactregistry v0.0.0
|
||||
git.novox.be/novox/mesh-sdk/go v0.0.0
|
||||
)
|
||||
@@ -5,6 +5,10 @@
|
||||
"build": {
|
||||
"artifacts": [
|
||||
{ "name": "go", "kind": "package", "language": "go", "paths": ["go"], "series": "0.1", "generated-from": "." },
|
||||
{ "name": "go-containerimageregistryclient", "kind": "package", "language": "go", "paths": ["go/clients/containerimageregistryclient"], "series": "0.1" },
|
||||
{ "name": "go-gopackageregistryclient", "kind": "package", "language": "go", "paths": ["go/clients/gopackageregistryclient"], "series": "0.1" },
|
||||
{ "name": "go-npmpackageregistryclient", "kind": "package", "language": "go", "paths": ["go/clients/npmpackageregistryclient"], "series": "0.1" },
|
||||
{ "name": "go-ociartifactregistryclient", "kind": "package", "language": "go", "paths": ["go/clients/ociartifactregistryclient"], "series": "0.1" },
|
||||
{ "name": "go-containerimageregistry", "kind": "package", "language": "go", "paths": ["go/seats/containerimageregistry"], "series": "0.1", "generated-from": "." },
|
||||
{ "name": "go-gopackageregistry", "kind": "package", "language": "go", "paths": ["go/seats/gopackageregistry"], "series": "0.1", "generated-from": "." },
|
||||
{ "name": "go-meshbroker", "kind": "package", "language": "go", "paths": ["go/seats/meshbroker"], "series": "0.1", "generated-from": "." },
|
||||
|
||||
Reference in New Issue
Block a user