Skip to content

Commit acb2737

Browse files
authored
feat: download object as json and csv (#17)
1 parent 7d3948c commit acb2737

62 files changed

Lines changed: 2759 additions & 659 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@ RUN mkdir /data && chmod 755 -R /data
1818
FROM gcr.io/distroless/static-debian12:nonroot
1919

2020
# Copy the binary to the production image from the builder stage.
21+
COPY --from=builder /etc/mime.types /etc/mime.types
2122
COPY --from=builder /app/ndc-cli /ndc-cli
2223
COPY --from=builder --chown=65532:65532 /data /home/nonroot/data
23-
2424
ENV HASURA_CONFIGURATION_DIRECTORY=/etc/connector
2525

2626
ENTRYPOINT ["/ndc-cli"]

compose.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ services:
9696

9797
# https://github.com/fsouza/fake-gcs-server
9898
gcp-storage-emulator:
99-
image: fsouza/fake-gcs-server:1.52.1
99+
image: fsouza/fake-gcs-server:1.52.2
100100
command:
101101
[
102102
"-public-host",

connector/functions/object.go

Lines changed: 79 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ package functions
22

33
import (
44
"context"
5+
"fmt"
56
"io"
67

78
"github.com/hasura/ndc-sdk-go/scalar"
89
"github.com/hasura/ndc-sdk-go/schema"
910
"github.com/hasura/ndc-sdk-go/utils"
1011
"github.com/hasura/ndc-storage/connector/collection"
1112
"github.com/hasura/ndc-storage/connector/storage/common"
13+
"github.com/hasura/ndc-storage/connector/storage/common/encoding"
1214
"github.com/hasura/ndc-storage/connector/types"
1315
)
1416

@@ -104,7 +106,7 @@ func FunctionStorageObject(ctx context.Context, state *types.State, args *common
104106

105107
// FunctionDownloadStorageObjectAsBase64 returns a stream of the object data. Most of the common errors occur when reading the stream.
106108
func FunctionDownloadStorageObjectAsBase64(ctx context.Context, state *types.State, args *common.GetStorageObjectArguments) (*DownloadStorageObjectResponse, error) {
107-
reader, err := downloadStorageObject(ctx, state, args)
109+
_, reader, err := downloadStorageObject(ctx, state, args)
108110
if err != nil {
109111
return nil, err
110112
}
@@ -127,7 +129,7 @@ func FunctionDownloadStorageObjectAsBase64(ctx context.Context, state *types.Sta
127129

128130
// FunctionDownloadStorageObjectAsText returns the object content in plain text. Use this function only if you know exactly the file as an text file.
129131
func FunctionDownloadStorageObjectAsText(ctx context.Context, state *types.State, args *common.GetStorageObjectArguments) (*DownloadStorageObjectTextResponse, error) {
130-
reader, err := downloadStorageObject(ctx, state, args)
132+
_, reader, err := downloadStorageObject(ctx, state, args)
131133
if err != nil {
132134
return nil, err
133135
}
@@ -148,17 +150,89 @@ func FunctionDownloadStorageObjectAsText(ctx context.Context, state *types.State
148150
return &DownloadStorageObjectTextResponse{Data: dataStr}, nil
149151
}
150152

151-
func downloadStorageObject(ctx context.Context, state *types.State, args *common.GetStorageObjectArguments) (io.ReadCloser, error) {
153+
// FunctionDownloadStorageObjectAsJson returns the object content in arbitrary json. Returns error if the content is unable to be decoded.
154+
func FunctionDownloadStorageObjectAsJson(ctx context.Context, state *types.State, args *common.GetStorageObjectArguments) (*DownloadStorageObjectJsonResponse, error) {
155+
stat, reader, err := downloadStorageObject(ctx, state, args)
156+
if err != nil {
157+
return nil, err
158+
}
159+
160+
if reader == nil {
161+
return nil, nil
162+
}
163+
164+
defer reader.Close()
165+
166+
var contentType string
167+
if stat.ContentType != nil {
168+
contentType = *stat.ContentType
169+
}
170+
171+
data, err := encoding.DecodeArbitraryData(ctx, stat.Name, contentType, reader)
172+
if err != nil {
173+
return nil, schema.UnprocessableContentError(err.Error(), nil)
174+
}
175+
176+
return &DownloadStorageObjectJsonResponse{Data: data}, nil
177+
}
178+
179+
// FunctionDownloadStorageObjectAsCsv downloads and decode the object content from CSV. Returns error if the content is unable to be decoded.
180+
func FunctionDownloadStorageObjectAsCsv(ctx context.Context, state *types.State, args *common.DownloadStorageObjectAsCsvArguments) (*DownloadStorageObjectJsonResponse, error) {
181+
getArgs := args.GetStorageObjectArguments
182+
getArgs.PreValidate = func(so *common.StorageObject) error {
183+
var contentType string
184+
if so.ContentType != nil {
185+
contentType = *so.ContentType
186+
}
187+
188+
if !encoding.IsValidCSVObject(so.Name, contentType) {
189+
return fmt.Errorf("failed to decode file %s to csv, unsupported content type %s", so.Name, contentType)
190+
}
191+
192+
return nil
193+
}
194+
195+
stat, reader, err := downloadStorageObject(ctx, state, &args.GetStorageObjectArguments)
196+
if err != nil {
197+
return nil, err
198+
}
199+
200+
if reader == nil {
201+
return nil, nil
202+
}
203+
204+
defer reader.Close()
205+
206+
decodeOptions := args.Options
207+
208+
if decodeOptions.Delimiter == "" {
209+
var contentType string
210+
if stat.ContentType != nil {
211+
contentType = *stat.ContentType
212+
}
213+
214+
decodeOptions.Delimiter = encoding.CSVCommaFromContentType(stat.Name, contentType)
215+
}
216+
217+
data, err := encoding.DecodeCSV(ctx, reader, decodeOptions)
218+
if err != nil {
219+
return nil, schema.UnprocessableContentError(err.Error(), nil)
220+
}
221+
222+
return &DownloadStorageObjectJsonResponse{Data: data}, nil
223+
}
224+
225+
func downloadStorageObject(ctx context.Context, state *types.State, args *common.GetStorageObjectArguments) (*common.StorageObject, io.ReadCloser, error) {
152226
request, err := collection.EvalObjectPredicate(args.StorageBucketArguments, &collection.StringComparisonOperator{
153227
Value: args.Name,
154228
Operator: collection.OperatorEqual,
155229
}, args.Where, types.QueryVariablesFromContext(ctx))
156230
if err != nil {
157-
return nil, err
231+
return nil, nil, err
158232
}
159233

160234
if !request.IsValid {
161-
return nil, nil
235+
return nil, nil, nil
162236
}
163237

164238
return state.Storage.GetObject(ctx, request.GetBucketArguments(), request.ObjectNamePredicate.GetPrefix(), args.GetStorageObjectOptions)

connector/functions/types.generated.go

Lines changed: 83 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

connector/functions/types.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,11 @@ type DownloadStorageObjectTextResponse struct {
4545
Data string `json:"data"`
4646
}
4747

48+
// DownloadStorageObjectJsonResponse represents the object data response in arbitrary JSON format.
49+
type DownloadStorageObjectJsonResponse struct {
50+
Data any `json:"data"`
51+
}
52+
4853
// PutStorageObjectArguments represents input arguments of the PutObject method.
4954
type PutStorageObjectBase64Arguments struct {
5055
common.PutStorageObjectArguments

0 commit comments

Comments
 (0)