github.com/instill-ai/component@v0.16.0-beta/pkg/base/component.go (about)

     1  package base
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"github.com/gofrs/uuid"
     9  	"github.com/instill-ai/component/pkg/jsonref"
    10  	"github.com/lestrrat-go/jsref/provider"
    11  	"go.uber.org/zap"
    12  	"golang.org/x/text/cases"
    13  	"golang.org/x/text/language"
    14  	"google.golang.org/protobuf/encoding/protojson"
    15  	"google.golang.org/protobuf/proto"
    16  	"google.golang.org/protobuf/types/known/structpb"
    17  
    18  	pipelinePB "github.com/instill-ai/protogen-go/vdp/pipeline/v1beta"
    19  )
    20  
    21  const conditionJSON = `
    22  {
    23  	"type": "string",
    24  	"instillUIOrder": 1,
    25  	"instillShortDescription": "config whether the component will be executed or skipped",
    26  	"instillAcceptFormats": ["string"],
    27      "instillUpstreamTypes": ["value", "template"]
    28  }
    29  `
    30  
    31  // IComponent is the interface that wraps the basic component methods.
    32  // All component need to implement this interface.
    33  type IComponent interface {
    34  	GetID() string
    35  	GetUID() uuid.UUID
    36  	GetLogger() *zap.Logger
    37  	GetUsageHandler() UsageHandler
    38  	GetTaskInputSchemas() map[string]string
    39  	GetTaskOutputSchemas() map[string]string
    40  }
    41  
    42  func convertDataSpecToCompSpec(dataSpec *structpb.Struct) (*structpb.Struct, error) {
    43  	// var err error
    44  	compSpec := proto.Clone(dataSpec).(*structpb.Struct)
    45  	if _, ok := compSpec.Fields["const"]; ok {
    46  		return compSpec, nil
    47  	}
    48  
    49  	isFreeform := checkFreeForm(compSpec)
    50  
    51  	if _, ok := compSpec.Fields["type"]; !ok && !isFreeform {
    52  		return nil, fmt.Errorf("type missing: %+v", compSpec)
    53  	} else if _, ok := compSpec.Fields["instillUpstreamTypes"]; !ok && compSpec.Fields["type"].GetStringValue() == "object" {
    54  
    55  		if _, ok := compSpec.Fields["instillUIOrder"]; !ok {
    56  			compSpec.Fields["instillUIOrder"] = structpb.NewNumberValue(0)
    57  		}
    58  		if _, ok := compSpec.Fields["required"]; !ok {
    59  			return nil, fmt.Errorf("required missing: %+v", compSpec)
    60  		}
    61  		if _, ok := compSpec.Fields["instillEditOnNodeFields"]; !ok {
    62  			compSpec.Fields["instillEditOnNodeFields"] = compSpec.Fields["required"]
    63  		}
    64  
    65  		if _, ok := compSpec.Fields["properties"]; ok {
    66  			for k, v := range compSpec.Fields["properties"].GetStructValue().AsMap() {
    67  				s, err := structpb.NewStruct(v.(map[string]interface{}))
    68  				if err != nil {
    69  					return nil, err
    70  				}
    71  				converted, err := convertDataSpecToCompSpec(s)
    72  				if err != nil {
    73  					return nil, err
    74  				}
    75  				compSpec.Fields["properties"].GetStructValue().Fields[k] = structpb.NewStructValue(converted)
    76  
    77  			}
    78  		}
    79  		if _, ok := compSpec.Fields["patternProperties"]; ok {
    80  			for k, v := range compSpec.Fields["patternProperties"].GetStructValue().AsMap() {
    81  				s, err := structpb.NewStruct(v.(map[string]interface{}))
    82  				if err != nil {
    83  					return nil, err
    84  				}
    85  				converted, err := convertDataSpecToCompSpec(s)
    86  				if err != nil {
    87  					return nil, err
    88  				}
    89  				compSpec.Fields["patternProperties"].GetStructValue().Fields[k] = structpb.NewStructValue(converted)
    90  
    91  			}
    92  		}
    93  		for _, target := range []string{"allOf", "anyOf", "oneOf"} {
    94  			if _, ok := compSpec.Fields[target]; ok {
    95  				for idx, item := range compSpec.Fields[target].GetListValue().AsSlice() {
    96  					s, err := structpb.NewStruct(item.(map[string]interface{}))
    97  					if err != nil {
    98  						return nil, err
    99  					}
   100  					converted, err := convertDataSpecToCompSpec(s)
   101  					if err != nil {
   102  						return nil, err
   103  					}
   104  					compSpec.Fields[target].GetListValue().AsSlice()[idx] = structpb.NewStructValue(converted)
   105  				}
   106  			}
   107  		}
   108  
   109  	} else {
   110  		if _, ok := compSpec.Fields["instillUIOrder"]; !ok {
   111  			compSpec.Fields["instillUIOrder"] = structpb.NewNumberValue(0)
   112  		}
   113  		original := proto.Clone(compSpec).(*structpb.Struct)
   114  		delete(original.Fields, "title")
   115  		delete(original.Fields, "description")
   116  		delete(original.Fields, "instillShortDescription")
   117  		delete(original.Fields, "instillAcceptFormats")
   118  		delete(original.Fields, "instillUIOrder")
   119  		delete(original.Fields, "instillUpstreamTypes")
   120  
   121  		newCompSpec := &structpb.Struct{Fields: make(map[string]*structpb.Value)}
   122  
   123  		newCompSpec.Fields["title"] = structpb.NewStringValue(compSpec.Fields["title"].GetStringValue())
   124  		newCompSpec.Fields["description"] = structpb.NewStringValue(compSpec.Fields["description"].GetStringValue())
   125  		if _, ok := compSpec.Fields["instillShortDescription"]; ok {
   126  			newCompSpec.Fields["instillShortDescription"] = compSpec.Fields["instillShortDescription"]
   127  		} else {
   128  			newCompSpec.Fields["instillShortDescription"] = newCompSpec.Fields["description"]
   129  		}
   130  		newCompSpec.Fields["instillUIOrder"] = structpb.NewNumberValue(compSpec.Fields["instillUIOrder"].GetNumberValue())
   131  		if compSpec.Fields["instillAcceptFormats"] != nil {
   132  			newCompSpec.Fields["instillAcceptFormats"] = structpb.NewListValue(compSpec.Fields["instillAcceptFormats"].GetListValue())
   133  		}
   134  		newCompSpec.Fields["instillUpstreamTypes"] = structpb.NewListValue(compSpec.Fields["instillUpstreamTypes"].GetListValue())
   135  		newCompSpec.Fields["anyOf"] = structpb.NewListValue(&structpb.ListValue{Values: []*structpb.Value{}})
   136  
   137  		for _, v := range compSpec.Fields["instillUpstreamTypes"].GetListValue().GetValues() {
   138  			if v.GetStringValue() == "value" {
   139  				original.Fields["instillUpstreamType"] = v
   140  				newCompSpec.Fields["anyOf"].GetListValue().Values = append(newCompSpec.Fields["anyOf"].GetListValue().Values, structpb.NewStructValue(original))
   141  			}
   142  			if v.GetStringValue() == "reference" {
   143  				item, err := structpb.NewValue(
   144  					map[string]interface{}{
   145  						"type":                "string",
   146  						"pattern":             "^\\{.*\\}$",
   147  						"instillUpstreamType": "reference",
   148  					},
   149  				)
   150  				if err != nil {
   151  					return nil, err
   152  				}
   153  				newCompSpec.Fields["anyOf"].GetListValue().Values = append(newCompSpec.Fields["anyOf"].GetListValue().Values, item)
   154  			}
   155  			if v.GetStringValue() == "template" {
   156  				item, err := structpb.NewValue(
   157  					map[string]interface{}{
   158  						"type":                "string",
   159  						"instillUpstreamType": "template",
   160  					},
   161  				)
   162  				if err != nil {
   163  					return nil, err
   164  				}
   165  				newCompSpec.Fields["anyOf"].GetListValue().Values = append(newCompSpec.Fields["anyOf"].GetListValue().Values, item)
   166  			}
   167  
   168  		}
   169  
   170  		compSpec = newCompSpec
   171  
   172  	}
   173  	return compSpec, nil
   174  }
   175  
   176  const taskPrefix = "TASK_"
   177  
   178  // TaskIDToTitle builds a Task title from its ID. This is used when the `title`
   179  // key in the task definition isn't present.
   180  func TaskIDToTitle(id string) string {
   181  	title := strings.ReplaceAll(id, taskPrefix, "")
   182  	title = strings.ReplaceAll(title, "_", " ")
   183  	return cases.Title(language.English).String(title)
   184  }
   185  
   186  func generateComponentTaskCards(tasks map[string]*structpb.Struct) []*pipelinePB.ComponentTask {
   187  	taskCards := make([]*pipelinePB.ComponentTask, 0, len(tasks))
   188  	for k := range tasks {
   189  		title := tasks[k].Fields["title"].GetStringValue()
   190  		if title == "" {
   191  			title = TaskIDToTitle(k)
   192  		}
   193  
   194  		description := tasks[k].Fields["instillShortDescription"].GetStringValue()
   195  
   196  		taskCards = append(taskCards, &pipelinePB.ComponentTask{
   197  			Name:        k,
   198  			Title:       title,
   199  			Description: description,
   200  		})
   201  	}
   202  
   203  	return taskCards
   204  }
   205  
   206  func generateComponentSpec(title string, tasks []*pipelinePB.ComponentTask, taskStructs map[string]*structpb.Struct) (*structpb.Struct, error) {
   207  	var err error
   208  	componentSpec := &structpb.Struct{Fields: map[string]*structpb.Value{}}
   209  	componentSpec.Fields["$schema"] = structpb.NewStringValue("http://json-schema.org/draft-07/schema#")
   210  	componentSpec.Fields["title"] = structpb.NewStringValue(fmt.Sprintf("%s Component", title))
   211  	componentSpec.Fields["type"] = structpb.NewStringValue("object")
   212  
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	oneOfList := &structpb.ListValue{
   218  		Values: []*structpb.Value{},
   219  	}
   220  	for _, task := range tasks {
   221  		taskName := task.Name
   222  
   223  		oneOf := &structpb.Struct{Fields: map[string]*structpb.Value{}}
   224  		oneOf.Fields["type"] = structpb.NewStringValue("object")
   225  		oneOf.Fields["properties"] = structpb.NewStructValue(&structpb.Struct{Fields: make(map[string]*structpb.Value)})
   226  
   227  		oneOf.Fields["properties"].GetStructValue().Fields["task"], err = structpb.NewValue(map[string]interface{}{
   228  			"const": task.Name,
   229  			"title": task.Title,
   230  		})
   231  		if err != nil {
   232  			return nil, err
   233  		}
   234  
   235  		if taskStructs[taskName].Fields["description"].GetStringValue() != "" {
   236  			oneOf.Fields["properties"].GetStructValue().Fields["task"].GetStructValue().Fields["description"] = structpb.NewStringValue(taskStructs[taskName].Fields["description"].GetStringValue())
   237  		}
   238  
   239  		if task.Description != "" {
   240  			oneOf.Fields["properties"].GetStructValue().Fields["task"].GetStructValue().Fields["instillShortDescription"] = structpb.NewStringValue(task.Description)
   241  		}
   242  		taskJSONStruct := proto.Clone(taskStructs[taskName]).(*structpb.Struct).Fields["input"].GetStructValue()
   243  
   244  		compInputStruct, err := convertDataSpecToCompSpec(taskJSONStruct)
   245  		if err != nil {
   246  			return nil, fmt.Errorf("task %s: %s error: %+v", title, task, err)
   247  		}
   248  
   249  		condition := &structpb.Struct{}
   250  		err = protojson.Unmarshal([]byte(conditionJSON), condition)
   251  		if err != nil {
   252  			if err != nil {
   253  				panic(err)
   254  			}
   255  		}
   256  		oneOf.Fields["properties"].GetStructValue().Fields["condition"] = structpb.NewStructValue(condition)
   257  		oneOf.Fields["properties"].GetStructValue().Fields["input"] = structpb.NewStructValue(compInputStruct)
   258  		if taskStructs[taskName].Fields["metadata"] != nil {
   259  			metadataStruct := proto.Clone(taskStructs[taskName]).(*structpb.Struct).Fields["metadata"].GetStructValue()
   260  			oneOf.Fields["properties"].GetStructValue().Fields["metadata"] = structpb.NewStructValue(metadataStruct)
   261  		}
   262  
   263  		// oneOf
   264  		oneOfList.Values = append(oneOfList.Values, structpb.NewStructValue(oneOf))
   265  	}
   266  
   267  	componentSpec.Fields["oneOf"] = structpb.NewListValue(oneOfList)
   268  
   269  	if err != nil {
   270  		return nil, err
   271  	}
   272  
   273  	return componentSpec, nil
   274  
   275  }
   276  
   277  func formatDataSpec(dataSpec *structpb.Struct) (*structpb.Struct, error) {
   278  	// var err error
   279  	compSpec := proto.Clone(dataSpec).(*structpb.Struct)
   280  	if _, ok := compSpec.Fields["const"]; ok {
   281  		return compSpec, nil
   282  	}
   283  
   284  	isFreeform := checkFreeForm(compSpec)
   285  
   286  	if _, ok := compSpec.Fields["type"]; !ok && !isFreeform {
   287  		return nil, fmt.Errorf("type missing: %+v", compSpec)
   288  	} else if compSpec.Fields["type"].GetStringValue() == "array" {
   289  
   290  		if _, ok := compSpec.Fields["instillUIOrder"]; !ok {
   291  			compSpec.Fields["instillUIOrder"] = structpb.NewNumberValue(0)
   292  		}
   293  
   294  		converted, err := formatDataSpec(compSpec.Fields["items"].GetStructValue())
   295  		if err != nil {
   296  			return nil, err
   297  		}
   298  		compSpec.Fields["items"] = structpb.NewStructValue(converted)
   299  	} else if compSpec.Fields["type"].GetStringValue() == "object" {
   300  
   301  		if _, ok := compSpec.Fields["instillUIOrder"]; !ok {
   302  			compSpec.Fields["instillUIOrder"] = structpb.NewNumberValue(0)
   303  		}
   304  		if _, ok := compSpec.Fields["required"]; !ok {
   305  			return nil, fmt.Errorf("required missing: %+v", compSpec)
   306  		}
   307  		if _, ok := compSpec.Fields["instillEditOnNodeFields"]; !ok {
   308  			compSpec.Fields["instillEditOnNodeFields"] = compSpec.Fields["required"]
   309  		}
   310  
   311  		if _, ok := compSpec.Fields["properties"]; ok {
   312  			for k, v := range compSpec.Fields["properties"].GetStructValue().AsMap() {
   313  				s, err := structpb.NewStruct(v.(map[string]interface{}))
   314  				if err != nil {
   315  					return nil, err
   316  				}
   317  				converted, err := formatDataSpec(s)
   318  				if err != nil {
   319  					return nil, err
   320  				}
   321  				compSpec.Fields["properties"].GetStructValue().Fields[k] = structpb.NewStructValue(converted)
   322  
   323  			}
   324  		}
   325  		if _, ok := compSpec.Fields["patternProperties"]; ok {
   326  			for k, v := range compSpec.Fields["patternProperties"].GetStructValue().AsMap() {
   327  				s, err := structpb.NewStruct(v.(map[string]interface{}))
   328  				if err != nil {
   329  					return nil, err
   330  				}
   331  				converted, err := formatDataSpec(s)
   332  				if err != nil {
   333  					return nil, err
   334  				}
   335  				compSpec.Fields["patternProperties"].GetStructValue().Fields[k] = structpb.NewStructValue(converted)
   336  
   337  			}
   338  		}
   339  		for _, target := range []string{"allOf", "anyOf", "oneOf"} {
   340  			if _, ok := compSpec.Fields[target]; ok {
   341  				for idx, item := range compSpec.Fields[target].GetListValue().AsSlice() {
   342  					s, err := structpb.NewStruct(item.(map[string]interface{}))
   343  					if err != nil {
   344  						return nil, err
   345  					}
   346  					converted, err := formatDataSpec(s)
   347  					if err != nil {
   348  						return nil, err
   349  					}
   350  					compSpec.Fields[target].GetListValue().AsSlice()[idx] = structpb.NewStructValue(converted)
   351  				}
   352  			}
   353  		}
   354  
   355  	} else {
   356  		if _, ok := compSpec.Fields["instillUIOrder"]; !ok {
   357  			compSpec.Fields["instillUIOrder"] = structpb.NewNumberValue(0)
   358  		}
   359  
   360  		newCompSpec := &structpb.Struct{Fields: make(map[string]*structpb.Value)}
   361  
   362  		newCompSpec.Fields["type"] = structpb.NewStringValue(compSpec.Fields["type"].GetStringValue())
   363  		newCompSpec.Fields["title"] = structpb.NewStringValue(compSpec.Fields["title"].GetStringValue())
   364  		newCompSpec.Fields["description"] = structpb.NewStringValue(compSpec.Fields["description"].GetStringValue())
   365  		if _, ok := newCompSpec.Fields["instillShortDescription"]; ok {
   366  			newCompSpec.Fields["instillShortDescription"] = compSpec.Fields["instillShortDescription"]
   367  		} else {
   368  			newCompSpec.Fields["instillShortDescription"] = newCompSpec.Fields["description"]
   369  		}
   370  		newCompSpec.Fields["instillUIOrder"] = structpb.NewNumberValue(compSpec.Fields["instillUIOrder"].GetNumberValue())
   371  		if compSpec.Fields["instillFormat"] != nil {
   372  			newCompSpec.Fields["instillFormat"] = structpb.NewStringValue(compSpec.Fields["instillFormat"].GetStringValue())
   373  		}
   374  
   375  		compSpec = newCompSpec
   376  
   377  	}
   378  	return compSpec, nil
   379  }
   380  
   381  func generateDataSpecs(tasks map[string]*structpb.Struct) (map[string]*pipelinePB.DataSpecification, error) {
   382  
   383  	specs := map[string]*pipelinePB.DataSpecification{}
   384  	for k := range tasks {
   385  		spec := &pipelinePB.DataSpecification{}
   386  		var err error
   387  		taskJSONStruct := proto.Clone(tasks[k]).(*structpb.Struct)
   388  		spec.Input, err = formatDataSpec(taskJSONStruct.Fields["input"].GetStructValue())
   389  		if err != nil {
   390  			return nil, err
   391  		}
   392  		spec.Output, err = formatDataSpec(taskJSONStruct.Fields["output"].GetStructValue())
   393  		if err != nil {
   394  			return nil, err
   395  		}
   396  		specs[k] = spec
   397  	}
   398  
   399  	return specs, nil
   400  }
   401  
   402  func loadTasks(availableTasks []string, tasksJSONBytes []byte) ([]*pipelinePB.ComponentTask, map[string]*structpb.Struct, error) {
   403  
   404  	taskStructs := map[string]*structpb.Struct{}
   405  	var err error
   406  
   407  	tasksJSONMap := map[string]map[string]interface{}{}
   408  	err = json.Unmarshal(tasksJSONBytes, &tasksJSONMap)
   409  	if err != nil {
   410  		return nil, nil, err
   411  	}
   412  
   413  	for _, t := range availableTasks {
   414  		if v, ok := tasksJSONMap[t]; ok {
   415  			taskStructs[t], err = structpb.NewStruct(v)
   416  			if err != nil {
   417  				return nil, nil, err
   418  			}
   419  
   420  		}
   421  	}
   422  	tasks := generateComponentTaskCards(taskStructs)
   423  	return tasks, taskStructs, nil
   424  }
   425  
   426  // ConvertFromStructpb converts from structpb.Struct to a struct
   427  func ConvertFromStructpb(from *structpb.Struct, to interface{}) error {
   428  	inputJSON, err := protojson.Marshal(from)
   429  	if err != nil {
   430  		return err
   431  	}
   432  
   433  	err = json.Unmarshal(inputJSON, to)
   434  	if err != nil {
   435  		return err
   436  	}
   437  	return nil
   438  }
   439  
   440  // ConvertToStructpb converts from a struct to structpb.Struct
   441  func ConvertToStructpb(from interface{}) (*structpb.Struct, error) {
   442  	to := &structpb.Struct{}
   443  	outputJSON, err := json.Marshal(from)
   444  	if err != nil {
   445  		return nil, err
   446  	}
   447  
   448  	err = protojson.Unmarshal(outputJSON, to)
   449  	if err != nil {
   450  		return nil, err
   451  	}
   452  	return to, nil
   453  }
   454  
   455  func RenderJSON(tasksJSONBytes []byte, additionalJSONBytes map[string][]byte) ([]byte, error) {
   456  	var err error
   457  	mp := provider.NewMap()
   458  	for k, v := range additionalJSONBytes {
   459  		var i interface{}
   460  		err = json.Unmarshal(v, &i)
   461  		if err != nil {
   462  			return nil, err
   463  		}
   464  		err = mp.Set(k, i)
   465  		if err != nil {
   466  			return nil, err
   467  		}
   468  	}
   469  	res := jsonref.New()
   470  	err = res.AddProvider(mp)
   471  	if err != nil {
   472  		return nil, err
   473  	}
   474  	err = res.AddProvider(provider.NewHTTP())
   475  	if err != nil {
   476  		return nil, err
   477  	}
   478  
   479  	var tasksJSON interface{}
   480  	err = json.Unmarshal(tasksJSONBytes, &tasksJSON)
   481  	if err != nil {
   482  		return nil, err
   483  	}
   484  
   485  	result, err := res.Resolve(tasksJSON, "", jsonref.WithRecursiveResolution(true))
   486  	if err != nil {
   487  		return nil, err
   488  	}
   489  	renderedTasksJSON, err := json.Marshal(result)
   490  	if err != nil {
   491  		return nil, err
   492  	}
   493  	return renderedTasksJSON, nil
   494  
   495  }
   496  
   497  // For formats such as `*`, `semi-structured/*`, and `semi-structured/json` we
   498  // treat them as freeform data. Thus, there is no need to set the `type` in the
   499  // JSON schema.
   500  func checkFreeForm(compSpec *structpb.Struct) bool {
   501  	acceptFormats := compSpec.Fields["instillAcceptFormats"].GetListValue().AsSlice()
   502  
   503  	formats := make([]any, 0, len(acceptFormats)+1) // This avoids reallocations when appending values to the slice.
   504  	formats = append(formats, acceptFormats...)
   505  
   506  	if instillFormat := compSpec.Fields["instillFormat"].GetStringValue(); instillFormat != "" {
   507  		formats = append(formats, instillFormat)
   508  	}
   509  
   510  	for _, v := range formats {
   511  		if v.(string) == "*" || v.(string) == "semi-structured/*" || v.(string) == "semi-structured/json" {
   512  			return true
   513  		}
   514  	}
   515  
   516  	return false
   517  }