package artifact import ( "context" "crypto/sha256" "errors" "fmt" "io" "mime" "os" "path/filepath" "gitea.maximumdirect.net/eric/scriptorium/internal/defaults" "gitea.maximumdirect.net/eric/scriptorium/internal/domain" ) var ( ErrUnsupportedRefType = errors.New("unsupported artifact reference type") ErrMissingInlineBody = errors.New("missing body for inline artifact") ErrMissingFilePath = errors.New("missing file path for file artifact") ) // Reader resolves artifact references into actual artifacts. type Reader interface { Read(ctx context.Context, ref domain.ArtifactRef) (*domain.Artifact, error) } // CompositeReader routes artifact resolution based on the reference type. type CompositeReader struct { inlineReader *inlineReader fileReader Reader } func NewCompositeReader() Reader { return &CompositeReader{ inlineReader: &inlineReader{}, fileReader: &fileReader{}, } } func (c *CompositeReader) Read(ctx context.Context, ref domain.ArtifactRef) (*domain.Artifact, error) { select { case <-ctx.Done(): return nil, ctx.Err() default: } switch ref.Type { case domain.ArtifactRefInline: return c.inlineReader.Read(ctx, ref) case domain.ArtifactRefFile: return c.fileReader.Read(ctx, ref) default: return nil, fmt.Errorf("%w: %s", ErrUnsupportedRefType, ref.Type) } } type inlineReader struct{} func (r *inlineReader) Read(ctx context.Context, ref domain.ArtifactRef) (*domain.Artifact, error) { select { case <-ctx.Done(): return nil, ctx.Err() default: } if ref.Body == "" { return nil, ErrMissingInlineBody } body := []byte(ref.Body) return &domain.Artifact{ ContentType: defaults.ContentTypeTextPlain, Body: body, Size: int64(len(body)), Hash: fmt.Sprintf("%x", sha256.Sum256(body)), URI: ref.URI, }, nil } type fileReader struct{} func (r *fileReader) Read(ctx context.Context, ref domain.ArtifactRef) (*domain.Artifact, error) { select { case <-ctx.Done(): return nil, ctx.Err() default: } if ref.URI == "" { return nil, ErrMissingFilePath } return readFileArtifact(ref.URI) } func readFileArtifact(path string) (*domain.Artifact, error) { file, err := os.Open(path) if err != nil { return nil, fmt.Errorf("failed to read file %s: %w", path, err) } defer file.Close() data, err := io.ReadAll(file) if err != nil { return nil, fmt.Errorf("failed to read file %s: %w", path, err) } contentType := mime.TypeByExtension(filepath.Ext(path)) if contentType == "" { contentType = defaults.ContentTypeTextPlain } return &domain.Artifact{ Name: filepath.Base(path), ContentType: contentType, Body: data, URI: path, Size: int64(len(data)), Hash: fmt.Sprintf("%x", sha256.Sum256(data)), }, nil }