diff --git a/api2_generated_models_test.go b/api2_generated_models_test.go
new file mode 100644
index 0000000..1076144
--- /dev/null
+++ b/api2_generated_models_test.go
@@ -0,0 +1,44 @@
+package transloadit
+
+// This file is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this file by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
+import (
+ "reflect"
+ "strings"
+ "testing"
+)
+
+func TestGeneratedApi2ContractModelFields(t *testing.T) {
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "AssemblyID", "assembly_id", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "AssemblySSLURL", "assembly_ssl_url", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "AssemblyURL", "assembly_url", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "Error", "error", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "Ok", "ok", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "Results", "results", reflect.TypeOf((*map[string][]*FileInfo)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "TUSURL", "tus_url", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(AssemblyInfo{}), "Uploads", "uploads", reflect.TypeOf((*[]*FileInfo)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(FileInfo{}), "Field", "field", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(FileInfo{}), "IsTUSFile", "is_tus_file", reflect.TypeOf((*bool)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(FileInfo{}), "Name", "name", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(FileInfo{}), "TUSUploadURL", "tus_upload_url", reflect.TypeOf((*string)(nil)).Elem())
+ assertGeneratedApi2ContractModelField(t, reflect.TypeOf(FileInfo{}), "UserMeta", "user_meta", reflect.TypeOf((*map[string]interface{})(nil)).Elem())
+}
+
+func assertGeneratedApi2ContractModelField(t *testing.T, modelType reflect.Type, fieldName string, jsonField string, expectedType reflect.Type) {
+ t.Helper()
+
+ field, ok := modelType.FieldByName(fieldName)
+ if !ok {
+ t.Fatalf("%s.%s is missing", modelType.Name(), fieldName)
+ }
+ if field.Type != expectedType {
+ t.Fatalf("%s.%s has type %s, expected %s", modelType.Name(), fieldName, field.Type, expectedType)
+ }
+
+ jsonTag := field.Tag.Get("json")
+ if jsonTag != jsonField && !strings.HasPrefix(jsonTag, jsonField+",") {
+ t.Fatalf("%s.%s has json tag %q, expected %q", modelType.Name(), fieldName, jsonTag, jsonField)
+ }
+}
diff --git a/assembly.go b/assembly.go
index bc7ce92..2592c88 100644
--- a/assembly.go
+++ b/assembly.go
@@ -1,13 +1,17 @@
package transloadit
import (
+ "bytes"
"context"
+ "encoding/base64"
"fmt"
"io"
"mime/multipart"
"net/http"
+ "net/url"
"os"
"strconv"
+ "strings"
"time"
)
@@ -86,6 +90,7 @@ type AssemblyInfo struct {
ParentID string `json:"parent_id"`
AssemblyURL string `json:"assembly_url"`
AssemblySSLURL string `json:"assembly_ssl_url"`
+ TUSURL string `json:"tus_url"`
BytesReceived int `json:"bytes_received"`
BytesExpected Integer `json:"bytes_expected"`
StartDate string `json:"start_date"`
@@ -135,9 +140,12 @@ type FileInfo struct {
OriginalMd5Hash string `json:"original_md5hash"`
OriginalID string `json:"original_id"`
OriginalBasename string `json:"original_basename"`
+ IsTUSFile bool `json:"is_tus_file"`
+ TUSUploadURL string `json:"tus_upload_url"`
URL string `json:"url"`
SSLURL string `json:"ssl_url"`
Meta map[string]interface{} `json:"meta"`
+ UserMeta map[string]interface{} `json:"user_meta"`
Cost int `json:"cost"`
}
@@ -233,6 +241,130 @@ func (client *Client) StartAssembly(ctx context.Context, assembly Assembly) (*As
return &info, err
}
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
+// CreateTusAssembly creates a TUS-ready Assembly that waits for the requested number of resumable uploads before execution continues.
+func (client *Client) CreateTusAssembly(ctx context.Context, fileCount int) (*AssemblyInfo, error) {
+ content := map[string]interface{}{
+ "await": false,
+ "steps": map[string]interface{}{
+ ":original": map[string]interface{}{
+ "output_meta": true,
+ "result": "debug",
+ "robot": "/upload/handle",
+ },
+ },
+ }
+ formFields := map[string]interface{}{
+ "num_expected_upload_files": fileCount,
+ }
+
+ var assembly AssemblyInfo
+ err := client.requestWithFormFields(ctx, "POST", "assemblies", content, formFields, &assembly)
+
+ return &assembly, err
+}
+
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
+// UploadTusAssembly creates a TUS-ready Assembly, uploads one file with the TUS protocol, and waits for the Assembly to finish.
+func (client *Client) UploadTusAssembly(ctx context.Context, fileCount int, content []byte, fieldname string, filename string, userMeta map[string]string) (*AssemblyInfo, string, error) {
+ createdAssembly, err := client.CreateTusAssembly(ctx, fileCount)
+ if err != nil {
+ return nil, "", err
+ }
+
+ endpointURL, err := url.Parse(createdAssembly.TUSURL)
+ if err != nil {
+ return nil, "", err
+ }
+
+ metadataMap := make(map[string]string)
+ for name, value := range userMeta {
+ metadataMap[name] = value
+ }
+ metadataMap["assembly_url"] = createdAssembly.AssemblyURL
+ metadataMap["fieldname"] = fieldname
+ metadataMap["filename"] = filename
+
+ createRequest, err := http.NewRequestWithContext(ctx, "POST", endpointURL.String(), nil)
+ if err != nil {
+ return nil, "", err
+ }
+ createRequest.Header.Set("Tus-Resumable", "1.0.0")
+ createRequest.Header.Set("Upload-Length", strconv.Itoa(len(content)))
+ metadataParts := make([]string, 0, len(metadataMap))
+ for name, value := range metadataMap {
+ metadataParts = append(metadataParts, fmt.Sprintf("%s %s", name, base64.StdEncoding.EncodeToString([]byte(value))))
+ }
+ createRequest.Header.Set("Upload-Metadata", strings.Join(metadataParts, ","))
+
+ createResponse, err := client.httpClient.Do(createRequest)
+ if err != nil {
+ return nil, "", err
+ }
+ defer createResponse.Body.Close()
+ if createResponse.StatusCode != 201 {
+ return nil, "", fmt.Errorf("TUS create returned HTTP %d, expected 201", createResponse.StatusCode)
+ }
+ uploadURLLocation := createResponse.Header.Get("Location")
+ if uploadURLLocation == "" {
+ return nil, "", fmt.Errorf("TUS create did not return a Location header")
+ }
+ uploadURL, err := endpointURL.Parse(uploadURLLocation)
+ if err != nil {
+ return nil, "", err
+ }
+ uploadURLText := uploadURL.String()
+
+ uploadRequest, err := http.NewRequestWithContext(ctx, "PATCH", uploadURLText, bytes.NewReader(content))
+ if err != nil {
+ return nil, "", err
+ }
+ uploadRequest.Header.Set("Tus-Resumable", "1.0.0")
+ uploadRequest.Header.Set("Upload-Offset", "0")
+ uploadRequest.Header.Set("Content-Type", "application/offset+octet-stream")
+
+ uploadResponse, err := client.httpClient.Do(uploadRequest)
+ if err != nil {
+ return nil, "", err
+ }
+ defer uploadResponse.Body.Close()
+ if uploadResponse.StatusCode != 204 {
+ return nil, "", fmt.Errorf("TUS upload returned HTTP %d, expected 204", uploadResponse.StatusCode)
+ }
+ uploadOffset, err := strconv.Atoi(uploadResponse.Header.Get("Upload-Offset"))
+ if err != nil {
+ return nil, "", err
+ }
+ if uploadOffset != len(content) {
+ return nil, "", fmt.Errorf("TUS upload offset %d, expected %d", uploadOffset, len(content))
+ }
+
+ createdAssemblyAssemblySSLURL := createdAssembly.AssemblySSLURL
+ if createdAssemblyAssemblySSLURL == "" {
+ return nil, "", fmt.Errorf("uploadTusAssembly needs createdAssembly.assembly_ssl_url")
+ }
+ completedAssembly, err := client.WaitForAssembly(ctx, createdAssembly)
+ if err != nil {
+ return nil, "", err
+ }
+
+ return completedAssembly, uploadURLText, nil
+}
+
+//
+
func (assembly *Assembly) makeRequest(ctx context.Context, client *Client) (*http.Request, error) {
// TODO: test with huge files
url := client.config.Endpoint + "/assemblies"
@@ -306,6 +438,12 @@ func (assembly *Assembly) makeRequest(ctx context.Context, client *Client) (*htt
return req, nil
}
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// GetAssembly fetches the full assembly status from the provided URL.
// The assembly URL must be absolute, for example:
// https://api2-amberly.transloadit.com/assemblies/15a6b3701d3811e78d7bfba4db1b053e
@@ -316,6 +454,14 @@ func (client *Client) GetAssembly(ctx context.Context, assemblyURL string) (*Ass
return &info, err
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// CancelAssembly cancels an assembly which will result in all corresponding
// uploads and encoding jobs to be aborted. Finally, the updated assembly
// information after the cancellation will be returned.
@@ -328,6 +474,8 @@ func (client *Client) CancelAssembly(ctx context.Context, assemblyURL string) (*
return &info, err
}
+//
+
// NewAssemblyReplay will create a new AssemblyReplay struct which can be used
// to replay an assemblie's execution using Client.StartAssemblyReplay.
// The assembly URL must be absolute, for example:
@@ -375,6 +523,12 @@ func (client *Client) StartAssemblyReplay(ctx context.Context, assembly Assembly
return &info, nil
}
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// ListAssemblies will fetch all assemblies matching the provided criteria.
func (client *Client) ListAssemblies(ctx context.Context, options *ListOptions) (AssemblyList, error) {
var assemblies AssemblyList
@@ -382,3 +536,5 @@ func (client *Client) ListAssemblies(ctx context.Context, options *ListOptions)
return assemblies, err
}
+
+//
diff --git a/assembly_test.go b/assembly_test.go
index a755514..b61ce30 100644
--- a/assembly_test.go
+++ b/assembly_test.go
@@ -264,3 +264,50 @@ func TestInteger_MarshalJSON(t *testing.T) {
t.Fatal("wrong default value for string")
}
}
+
+func TestAssemblyInfo_TusFields(t *testing.T) {
+ t.Parallel()
+
+ var info AssemblyInfo
+ err := json.Unmarshal([]byte(`{
+ "tus_url": "https://api2.example/resumable/files/",
+ "uploads": [
+ {
+ "is_tus_file": true,
+ "tus_upload_url": "https://api2.example/resumable/files/upload-id",
+ "user_meta": {
+ "hello": "world"
+ }
+ }
+ ],
+ "results": {
+ ":original": [
+ {
+ "is_tus_file": false,
+ "user_meta": {
+ "hello": "world"
+ }
+ }
+ ]
+ }
+ }`), &info)
+ if err != nil {
+ t.Fatal(err)
+ }
+
+ if info.TUSURL != "https://api2.example/resumable/files/" {
+ t.Fatal("wrong tus url")
+ }
+ if len(info.Uploads) != 1 || !info.Uploads[0].IsTUSFile {
+ t.Fatal("wrong TUS upload marker")
+ }
+ if info.Uploads[0].TUSUploadURL != "https://api2.example/resumable/files/upload-id" {
+ t.Fatal("wrong TUS upload url")
+ }
+ if info.Uploads[0].UserMeta["hello"] != "world" {
+ t.Fatal("wrong upload user meta")
+ }
+ if info.Results[":original"][0].UserMeta["hello"] != "world" {
+ t.Fatal("wrong result user meta")
+ }
+}
diff --git a/examples/api2-devdock-template-lifecycle/main.go b/examples/api2-devdock-template-lifecycle/main.go
new file mode 100644
index 0000000..7bc0451
--- /dev/null
+++ b/examples/api2-devdock-template-lifecycle/main.go
@@ -0,0 +1,226 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "time"
+
+ transloadit "github.com/transloadit/go-sdk"
+)
+
+type scenarioContent struct {
+ AdditionalProperties map[string]interface{} `json:"additionalProperties"`
+ Steps map[string]map[string]interface{} `json:"steps"`
+}
+
+type templateLifecycleScenario struct {
+ Delete struct {
+ ErrorCodeIncludes string `json:"errorCodeIncludes"`
+ } `json:"delete"`
+ List struct {
+ MinimumCount int `json:"minimumCount"`
+ PageSize int `json:"pageSize"`
+ } `json:"list"`
+ ScenarioID string `json:"scenarioId"`
+ Template struct {
+ Content scenarioContent `json:"content"`
+ NamePrefix string `json:"namePrefix"`
+ RequireSignatureAuth bool `json:"requireSignatureAuth"`
+ } `json:"template"`
+ Update struct {
+ Content scenarioContent `json:"content"`
+ NameSuffix string `json:"nameSuffix"`
+ RequireSignatureAuth bool `json:"requireSignatureAuth"`
+ } `json:"update"`
+}
+
+func requiredEnv(name string) string {
+ value := os.Getenv(name)
+ if value == "" {
+ panic(fmt.Sprintf("%s must be set", name))
+ }
+
+ return value
+}
+
+func fail(format string, args ...interface{}) {
+ panic(fmt.Sprintf(format, args...))
+}
+
+func loadScenario() (templateLifecycleScenario, error) {
+ scenarioPath := os.Getenv("API2_SDK_EXAMPLE_SCENARIO")
+ if scenarioPath == "" {
+ scenarioPath = filepath.Join(
+ "examples",
+ "api2-devdock-template-lifecycle",
+ "api2-scenario.json",
+ )
+ }
+
+ contents, err := ioutil.ReadFile(scenarioPath)
+ if err != nil {
+ return templateLifecycleScenario{}, err
+ }
+
+ var scenario templateLifecycleScenario
+ if err := json.Unmarshal(contents, &scenario); err != nil {
+ return templateLifecycleScenario{}, err
+ }
+
+ return scenario, nil
+}
+
+func applyTemplateContent(template *transloadit.Template, content scenarioContent) {
+ for stepName, step := range content.Steps {
+ template.AddStep(stepName, step)
+ }
+
+ for name, value := range content.AdditionalProperties {
+ template.Content.AdditionalProperties[name] = value
+ }
+}
+
+func newTemplate(name string, requireSignatureAuth bool, content scenarioContent) transloadit.Template {
+ template := transloadit.NewTemplate()
+ template.Name = name
+ template.RequireSignatureAuth = requireSignatureAuth
+ applyTemplateContent(&template, content)
+
+ return template
+}
+
+func main() {
+ ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
+ defer cancel()
+
+ scenario, err := loadScenario()
+ if err != nil {
+ fail("load scenario: %v", err)
+ }
+
+ client := transloadit.NewClient(transloadit.Config{
+ AuthKey: requiredEnv("TRANSLOADIT_KEY"),
+ AuthSecret: requiredEnv("TRANSLOADIT_SECRET"),
+ Endpoint: requiredEnv("TRANSLOADIT_ENDPOINT"),
+ })
+
+ templateName := fmt.Sprintf("%s-%d", scenario.Template.NamePrefix, time.Now().UnixNano())
+ template := newTemplate(
+ templateName,
+ scenario.Template.RequireSignatureAuth,
+ scenario.Template.Content,
+ )
+
+ templateID, err := client.CreateTemplate(ctx, template)
+ if err != nil {
+ fail("create template: %v", err)
+ }
+ if templateID == "" {
+ fail("create template returned an empty id")
+ }
+
+ deleteTemplate := true
+ defer func() {
+ if deleteTemplate {
+ _ = client.DeleteTemplate(context.Background(), templateID)
+ }
+ }()
+
+ fetched, err := client.GetTemplate(ctx, templateID)
+ if err != nil {
+ fail("get template: %v", err)
+ }
+
+ templateList, err := client.ListTemplates(ctx, &transloadit.ListOptions{
+ PageSize: scenario.List.PageSize,
+ })
+ if err != nil {
+ fail("list templates: %v", err)
+ }
+
+ updatedTemplate := newTemplate(
+ templateName+scenario.Update.NameSuffix,
+ scenario.Update.RequireSignatureAuth,
+ scenario.Update.Content,
+ )
+
+ if err := client.UpdateTemplate(ctx, templateID, updatedTemplate); err != nil {
+ fail("update template: %v", err)
+ }
+
+ fetchedUpdated, err := client.GetTemplate(ctx, templateID)
+ if err != nil {
+ fail("get updated template: %v", err)
+ }
+
+ if err := client.DeleteTemplate(ctx, templateID); err != nil {
+ fail("delete template: %v", err)
+ }
+ deleteTemplate = false
+
+ _, err = client.GetTemplate(ctx, templateID)
+ deletedGetSucceeded := err == nil
+ deletedErrorCode := ""
+ var requestErr transloadit.RequestError
+ if err != nil && !errors.As(err, &requestErr) {
+ fail("get deleted template returned %T, expected transloadit.RequestError", err)
+ }
+ if err != nil {
+ deletedErrorCode = requestErr.Code
+ }
+
+ result := map[string]interface{}{
+ "deletedErrorCode": deletedErrorCode,
+ "deletedGetSucceeded": deletedGetSucceeded,
+ "fetched": templateResult(fetched),
+ "listCount": templateList.Count,
+ "templateId": templateID,
+ "templateName": templateName,
+ "updated": templateResult(fetchedUpdated),
+ "updatedTemplateName": updatedTemplate.Name,
+ }
+ if err := writeResult(result); err != nil {
+ fail("write result: %v", err)
+ }
+
+ fmt.Printf(
+ "Go Transloadit SDK devdock scenario %s passed for %s\n",
+ scenario.ScenarioID,
+ requiredEnv("TRANSLOADIT_ENDPOINT"),
+ )
+}
+
+func templateResult(template transloadit.Template) map[string]interface{} {
+ content := map[string]interface{}{
+ "steps": template.Content.Steps,
+ }
+ for name, value := range template.Content.AdditionalProperties {
+ content[name] = value
+ }
+
+ return map[string]interface{}{
+ "content": content,
+ "id": template.ID,
+ "name": template.Name,
+ "requireSignatureAuth": template.RequireSignatureAuth,
+ }
+}
+
+func writeResult(result map[string]interface{}) error {
+ resultPath := os.Getenv("API2_SDK_EXAMPLE_RESULT")
+ if resultPath == "" {
+ return nil
+ }
+
+ contents, err := json.MarshalIndent(result, "", " ")
+ if err != nil {
+ return err
+ }
+
+ return ioutil.WriteFile(resultPath, append(contents, '\n'), 0o644)
+}
diff --git a/examples/api2-devdock-tus-assembly/main.go b/examples/api2-devdock-tus-assembly/main.go
new file mode 100644
index 0000000..c3996d7
--- /dev/null
+++ b/examples/api2-devdock-tus-assembly/main.go
@@ -0,0 +1,152 @@
+package main
+
+import (
+ "context"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "time"
+
+ transloadit "github.com/transloadit/go-sdk"
+)
+
+type tusAssemblyScenario struct {
+ ExampleInput struct {
+ ScenarioID string `json:"scenarioId"`
+ SdkFeatureInputs struct {
+ UploadTusAssembly uploadTusAssemblyInput `json:"uploadTusAssembly"`
+ } `json:"sdkFeatureInputs"`
+ } `json:"exampleInput"`
+}
+
+type uploadTusAssemblyInput struct {
+ FileCount int `json:"file_count"`
+ Upload uploadConfig `json:"upload"`
+}
+
+type uploadConfig struct {
+ Content string `json:"content"`
+ Field string `json:"fieldname"`
+ Filename string `json:"filename"`
+ UserMeta map[string]string `json:"user_meta"`
+}
+
+func requiredEnv(name string) string {
+ value := os.Getenv(name)
+ if value == "" {
+ panic(fmt.Sprintf("%s must be set", name))
+ }
+
+ return value
+}
+
+func fail(format string, args ...interface{}) {
+ panic(fmt.Sprintf(format, args...))
+}
+
+func loadScenario() (tusAssemblyScenario, error) {
+ scenarioPath := os.Getenv("API2_SDK_EXAMPLE_SCENARIO")
+ if scenarioPath == "" {
+ scenarioPath = filepath.Join("examples", "api2-devdock-tus-assembly", "api2-scenario.json")
+ }
+
+ contents, err := ioutil.ReadFile(scenarioPath)
+ if err != nil {
+ return tusAssemblyScenario{}, err
+ }
+
+ var scenario tusAssemblyScenario
+ if err := json.Unmarshal(contents, &scenario); err != nil {
+ return tusAssemblyScenario{}, err
+ }
+
+ return scenario, nil
+}
+
+func asJsonObject(value interface{}, label string) (map[string]interface{}, error) {
+ contents, err := json.Marshal(value)
+ if err != nil {
+ return nil, err
+ }
+
+ var result map[string]interface{}
+ if err := json.Unmarshal(contents, &result); err != nil {
+ return nil, err
+ }
+
+ return result, nil
+}
+
+func writeResult(
+ status map[string]interface{},
+ uploadURL string,
+) error {
+ resultPath := os.Getenv("API2_SDK_EXAMPLE_RESULT")
+ if resultPath == "" {
+ return nil
+ }
+
+ contents, err := json.MarshalIndent(
+ map[string]interface{}{
+ "createResponse": status,
+ "status": status,
+ "uploadUrl": uploadURL,
+ },
+ "",
+ " ",
+ )
+ if err != nil {
+ return err
+ }
+
+ return ioutil.WriteFile(resultPath, append(contents, '\n'), 0o644)
+}
+
+func main() {
+ ctx, cancel := context.WithTimeout(context.Background(), 90*time.Second)
+ defer cancel()
+
+ scenario, err := loadScenario()
+ if err != nil {
+ fail("load scenario: %v", err)
+ }
+ input := scenario.ExampleInput.SdkFeatureInputs.UploadTusAssembly
+
+ client := transloadit.NewClient(transloadit.Config{
+ AuthKey: requiredEnv("TRANSLOADIT_KEY"),
+ AuthSecret: requiredEnv("TRANSLOADIT_SECRET"),
+ Endpoint: requiredEnv("TRANSLOADIT_ENDPOINT"),
+ })
+
+ userMeta := input.Upload.UserMeta
+ if userMeta == nil {
+ userMeta = map[string]string{}
+ }
+
+ statusInfo, uploadURL, err := client.UploadTusAssembly(
+ ctx,
+ input.FileCount,
+ []byte(input.Upload.Content),
+ input.Upload.Field,
+ input.Upload.Filename,
+ userMeta,
+ )
+ if err != nil {
+ fail("upload TUS assembly: %v", err)
+ }
+ status, err := asJsonObject(statusInfo, "assembly status")
+ if err != nil {
+ fail("serialize assembly status: %v", err)
+ }
+ if err := writeResult(status, uploadURL); err != nil {
+ fail("write result: %v", err)
+ }
+
+ fmt.Printf(
+ "Go Transloadit SDK devdock scenario %s uploaded to %s\n",
+ scenario.ExampleInput.ScenarioID,
+ uploadURL,
+ )
+}
diff --git a/notification.go b/notification.go
index 5e14999..0532f7a 100644
--- a/notification.go
+++ b/notification.go
@@ -34,6 +34,12 @@ func (client *Client) ListNotifications(ctx context.Context, options *ListOption
return list, errors.New("transloadit: listing assembly notifications is no longer available")
}
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// ReplayNotification instructs the endpoint to replay the notification
// corresponding to the provided assembly ID.
// If notifyURL is not empty it will override the notify URL used in the
@@ -47,3 +53,5 @@ func (client *Client) ReplayNotification(ctx context.Context, assemblyID string,
return client.request(ctx, "POST", "assembly_notifications/"+assemblyID+"/replay", params, nil)
}
+
+//
diff --git a/template.go b/template.go
index 2c4f658..8549224 100644
--- a/template.go
+++ b/template.go
@@ -146,6 +146,12 @@ func (template *Template) UnmarshalJSON(b []byte) error {
return nil
}
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// CreateTemplate will save the provided template struct as a new template
// and return the ID of the new template.
func (client *Client) CreateTemplate(ctx context.Context, template Template) (string, error) {
@@ -164,6 +170,14 @@ func (client *Client) CreateTemplate(ctx context.Context, template Template) (st
return template.ID, nil
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// GetTemplate will retrieve details about the template associated with the
// provided template ID.
func (client *Client) GetTemplate(ctx context.Context, templateID string) (template Template, err error) {
@@ -171,12 +185,28 @@ func (client *Client) GetTemplate(ctx context.Context, templateID string) (templ
return template, err
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// DeleteTemplate will delete the template associated with the provided
// template ID.
func (client *Client) DeleteTemplate(ctx context.Context, templateID string) error {
return client.request(ctx, "DELETE", "templates/"+templateID, nil, nil)
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// UpdateTemplate will update the template associated with the provided
// template ID to match the new name and new content. Please be aware that you
// are not able to change a template's ID.
@@ -195,8 +225,18 @@ func (client *Client) UpdateTemplate(ctx context.Context, templateID string, new
return client.request(ctx, "PUT", "templates/"+templateID, content, nil)
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// ListTemplates will retrieve all templates matching the criteria.
func (client *Client) ListTemplates(ctx context.Context, options *ListOptions) (list TemplateList, err error) {
err = client.listRequest(ctx, "templates", options, &list)
return list, err
}
+
+//
diff --git a/template_credentials.go b/template_credentials.go
index 5c43765..4417bac 100644
--- a/template_credentials.go
+++ b/template_credentials.go
@@ -38,6 +38,12 @@ func NewTemplateCredential() TemplateCredential {
var templateCredentialPrefix = "template_credentials"
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// CreateTemplateCredential will save the provided template credential struct to the server
// and return the ID of the new template credential.
func (client *Client) CreateTemplateCredential(ctx context.Context, templateCredential TemplateCredential) (string, error) {
@@ -53,6 +59,14 @@ func (client *Client) CreateTemplateCredential(ctx context.Context, templateCred
return response.Credential.ID, nil
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// GetTemplateCredential will retrieve details about the template credential associated with the
// provided template credential ID.
func (client *Client) GetTemplateCredential(ctx context.Context, templateCredentialID string) (TemplateCredential, error) {
@@ -61,18 +75,42 @@ func (client *Client) GetTemplateCredential(ctx context.Context, templateCredent
return response.Credential, err
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// DeleteTemplateCredential will delete the template credential associated with the provided
// template ID.
func (client *Client) DeleteTemplateCredential(ctx context.Context, templateCredentialID string) error {
return client.request(ctx, "DELETE", templateCredentialPrefix+"/"+templateCredentialID, nil, nil)
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// ListTemplateCredential will retrieve all templates credential matching the criteria.
func (client *Client) ListTemplateCredential(ctx context.Context, options *ListOptions) (list TemplateCredentialList, err error) {
err = client.listRequest(ctx, templateCredentialPrefix, options, &list)
return list, err
}
+//
+
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
// UpdateTemplateCredential will update the template credential associated with the provided
// template credential ID to match the new name and new content.
func (client *Client) UpdateTemplateCredential(ctx context.Context, templateCredentialID string, templateCredential TemplateCredential) error {
@@ -83,3 +121,5 @@ func (client *Client) UpdateTemplateCredential(ctx context.Context, templateCred
}
return client.request(ctx, "PUT", templateCredentialPrefix+"/"+templateCredentialID, content, nil)
}
+
+//
diff --git a/transloadit.go b/transloadit.go
index acdb038..cba640b 100755
--- a/transloadit.go
+++ b/transloadit.go
@@ -155,6 +155,32 @@ func (client *Client) doRequest(req *http.Request, result interface{}) error {
}
func (client *Client) request(ctx context.Context, method string, path string, content map[string]interface{}, result interface{}) error {
+ return client.requestWithFormFields(ctx, method, path, content, nil, result)
+}
+
+func formFieldValue(value interface{}) string {
+ switch typed := value.(type) {
+ case nil:
+ return ""
+ case bool:
+ return strconv.FormatBool(typed)
+ case float32:
+ return strconv.FormatFloat(float64(typed), 'f', -1, 32)
+ case float64:
+ return strconv.FormatFloat(typed, 'f', -1, 64)
+ case string:
+ return typed
+ }
+
+ serialized, err := json.Marshal(value)
+ if err == nil {
+ return string(serialized)
+ }
+
+ return fmt.Sprint(value)
+}
+
+func (client *Client) requestWithFormFields(ctx context.Context, method string, path string, content map[string]interface{}, formFields map[string]interface{}, result interface{}) error {
uri := path
// Don't add host for absolute urls
if u, err := url.Parse(path); err == nil && u.Scheme == "" {
@@ -175,6 +201,9 @@ func (client *Client) request(ctx context.Context, method string, path string, c
v := url.Values{}
v.Set("params", params)
v.Set("signature", signature)
+ for name, value := range formFields {
+ v.Set(name, formFieldValue(value))
+ }
var body io.Reader
if method == "GET" {
diff --git a/transloadit_test.go b/transloadit_test.go
index d4eb73b..61f890b 100755
--- a/transloadit_test.go
+++ b/transloadit_test.go
@@ -52,6 +52,29 @@ func TestNewClient_Success(t *testing.T) {
_ = NewClient(config)
}
+func TestFormFieldValue(t *testing.T) {
+ t.Parallel()
+
+ cases := map[string]struct {
+ input interface{}
+ expected string
+ }{
+ "bool": {input: true, expected: "true"},
+ "int": {input: 3, expected: "3"},
+ "nil": {input: nil, expected: ""},
+ "object": {input: map[string]interface{}{"field": "value"}, expected: `{"field":"value"}`},
+ "string": {input: "file", expected: "file"},
+ }
+
+ for name, tc := range cases {
+ t.Run(name, func(t *testing.T) {
+ if actual := formFieldValue(tc.input); actual != tc.expected {
+ t.Fatalf("expected %q, got %q", tc.expected, actual)
+ }
+ })
+ }
+}
+
func setup(t *testing.T) Client {
config := DefaultConfig
config.AuthKey = os.Getenv("TRANSLOADIT_KEY")
diff --git a/wait.go b/wait.go
index d7118cd..b7990a2 100644
--- a/wait.go
+++ b/wait.go
@@ -5,9 +5,14 @@ import (
"time"
)
-// WaitForAssembly fetches continuously the assembly status until it has
-// finished uploading and executing or until an assembly error occurs.
-// If you want to end this loop prematurely, you can cancel the supplied context.
+//
+
+// This block is generated from Transloadit API2 contracts. If it looks wrong,
+// please report the issue instead of editing this block by hand; the source fix
+// belongs in the contract generator so all SDKs stay in sync.
+
+// WaitForAssembly waits for an Assembly to finish uploading and executing.
+// Use the returned assembly_ssl_url as the assembly URL.
func (client *Client) WaitForAssembly(ctx context.Context, assembly *AssemblyInfo) (*AssemblyInfo, error) {
for {
res, err := client.GetAssembly(ctx, assembly.AssemblySSLURL)
@@ -33,3 +38,5 @@ func (client *Client) WaitForAssembly(ctx context.Context, assembly *AssemblyInf
}
}
}
+
+//