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 }