package
0.6.8
Repository: https://github.com/theopenlane/core.git
Documentation: pkg.go.dev

# Packages

No description provided by the author
No description provided by the author

# README

Objects

Objects is a package designed to simplify the process of managing receipt of multipart/form-data requests and subsequently the uploading of files.

Usage

Assuming you have a HTML form like this:

<form action="/" method="post" enctype="multipart/form-data">
  <input type="file" name="form-field-1" />
  <input type="file" name="form-field-2" />
</form>

To create a new objects instance, you can do something like this:

objectManager, _ := objects.New(
 objects.WithMaxFileSize(10<<20),
 objects.WithKeys([]string{"form-field-1", "form-field-2"}),
 objects.WithUploaderFunc(
   func(ctx context.Context, u *Objects, files []FileUpload) ([]File, error) {
    // add your own custom uploader functionality here
    // or leave out to use the default uploader func
   }
 ),
 objects.WithValidationFunc(
  objects.ChainValidators(objects.MimeTypeValidator("image/jpeg", "image/png"),
   func(f objects.File) error {
    // Your own custom validation function on the file here
    return nil
   })),
 objects.WithStorage(s3Store),
)

The objectManager can be used with the provided middleware FileUploadMiddleware(objectManager) and added to the chain of middleware within the server. This is a generic http.Handler so it can be used with your router of choice. For example, to be used with echo:

echo.WrapMiddleware(objects.FileUploadMiddleware(objectManager))

Standard HTTP router

package main

import (
	"fmt"
	"net/http"

	"github.com/aws/aws-sdk-go-v2/aws"
	awsCreds "github.com/aws/aws-sdk-go-v2/credentials"

	"github.com/theopenlane/core/pkg/objects"
	"github.com/theopenlane/core/pkg/objects/storage"
)

func main() {
	s3Store, err := storage.NewS3FromConfig(aws.Config{
		Region: "us-west-2",
		Credentials: awsCreds.NewStaticCredentialsProvider(
			"accessKey", "secretKey", ""),
	}, storage.S3Options{
		Bucket: "std-router",
	})
	if err != nil {
		panic(err.Error())
	}

	objectsHandler, err := objects.New(
		objects.WithMaxFileSize(10<<20),
		objects.WithStorage(s3Store),
		objects.WithKeys([]string{"name", "mitb"}),
	)

	mux := http.NewServeMux()

	// upload all files with the "name" and "mitb" fields on this route
	mux.Handle("/", objects.FileUploadMiddleware(objectsHandler)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Println("Uploading file")

		ctx := r.Context()

		// return all uploaded files
		f, err := objects.FilesFromContext(ctx)
		if err != nil {
			fmt.Println(err)
			return
		}

		// return uploaded files with the form field "mitb"
		ff, err := objects.FilesFromContextWithKey(ctx, "mitb")
		if err != nil {
			fmt.Println(err)
			return
		}

		fmt.Printf("%+v", ff)

		for _, v := range f {
			fmt.Printf("%+v", v)
			fmt.Println()
		}
	})))

	http.ListenAndServe(":3300", mux)
}

Ignoring non existent keys in the multipart Request

Sometimes, the keys you have configured the middleware might get dropped from the frontend for some reason, ideally the middleware fails if it cannot find a configured key in the request. To disable this behavior and ignore the missing key, you can make use of the WithIgnoreNonExistentKey(true) option to prevent the middleware from causing an error when such keys do not exists

Customizing the error response

Since Objects could be used as a middleware, it returns an error to the client if found, this might not match your existing structure, so to configure the response, use the WithErrorResponseHandler. The default is shown below and can be used as a template to define yours.

var errHandler objects.ErrResponseHandler = func(err error, statusCode int) http.HandlerFunc {
  return func(w http.ResponseWriter, _ *http.Request) {
    w.Header().Set("Content-Type", "application/json")
    w.WriteHeader(statusCode)
    fmt.Fprintf(w, `{"message" : "could not upload file", "error" : "%s"}`, err.Error())
  }
}

// add the following to the objects options:
// objects.WithErrorResponseHandler(errHandler)

Writing your own custom upload logic

The uploader function by default will just upload the file to the storage backend. In some cases you may want custom logic, e.g. update your local database with information. A custom UploaderFunc can be used to do this.

var uploader objects.UploaderFunc = func(ctx context.Context, u *objects.Objects, files []objects.FileUpload) ([]objects.File, error) {
	uploadedFiles := make([]objects.File, 0, len(files))

	for _, f := range files {
		// do things
	}

	return uploadedFiles, nil
}

// add the following to the objects options:
// objects.WithUploaderFunc(uploader)

Writing your custom validator logic

Sometimes, you could have some custom logic to validate uploads, in this example below, we limit the size of the upload based on the mimeypes of the uploaded files

var customValidator objects.ValidationFunc = func(f objects.File) error {
 switch f.MimeType {
 case "image/png":
  if f.Size > 4096 {
   return errors.New("file size too large")
  }

  return nil

 case "application/pdf":
  if f.Size > (1024 * 10) {
   return errors.New("file size too large")
  }

  return nil
 default:
  return nil
 }
}

// add the following to the objects options:
// objects.WithValidationFunc(customValidator)