github.com/filecoin-project/bacalhau@v0.3.23-0.20230228154132-45c989550ace/pkg/model/v1beta1/ipld.go (about)

     1  package v1beta1
     2  
     3  import (
     4  	"bytes"
     5  	"embed"
     6  	"reflect"
     7  
     8  	"github.com/ipld/go-ipld-prime"
     9  	"github.com/ipld/go-ipld-prime/codec"
    10  	"github.com/ipld/go-ipld-prime/codec/json"
    11  	"github.com/ipld/go-ipld-prime/datamodel"
    12  	"github.com/ipld/go-ipld-prime/schema"
    13  )
    14  
    15  //go:embed schemas
    16  var schemas embed.FS
    17  
    18  const (
    19  	ucanTaskSchemaPath     string = "schemas/invocation.ipldsch"
    20  	bacalhauTaskSchemaPath string = "schemas/bacalhau.ipldsch"
    21  )
    22  
    23  func load(path string) *Schema {
    24  	file, err := schemas.Open(path)
    25  	if err != nil {
    26  		panic(err)
    27  	}
    28  
    29  	schema, err := ipld.LoadSchema(path, file)
    30  	if err != nil {
    31  		panic(err)
    32  	}
    33  	return (*Schema)(schema)
    34  }
    35  
    36  type Schema schema.TypeSystem
    37  
    38  var (
    39  	// The UCAN Task schema is the standardized Invocation IPLD schema, defined
    40  	// by https://github.com/ucan-wg/invocation.
    41  	UCANTaskSchema *Schema = load(ucanTaskSchemaPath)
    42  
    43  	// The Bacalhau schema includes the Bacalhau specific extensions to the UCAN
    44  	// Task IPLD spec, i.e. input structures for specific job types.
    45  	BacalhauTaskSchema *Schema = load(bacalhauTaskSchemaPath)
    46  )
    47  
    48  // GetSchemaTypeName returns the name of the corresponding IPLD type in the
    49  // schema for the passed Go object. If the type cannot be in the schema, it
    50  // returns an empty string. It may return a non-empty string even if the type is
    51  // not in the schema.
    52  func (s *Schema) GetSchemaTypeName(obj interface{}) string {
    53  	// Convention: all go types share the same name as their schema types
    54  	return reflect.TypeOf(obj).Elem().Name()
    55  }
    56  
    57  // GetSchemaType returns the IPLD type from the schema for the passed Go object.
    58  // If the type is not in the schema, it returns nil.
    59  func (s *Schema) GetSchemaType(obj interface{}) schema.Type {
    60  	name := s.GetSchemaTypeName(obj)
    61  	return (*schema.TypeSystem)(s).TypeByName(name)
    62  }
    63  
    64  // UnmarshalIPLD parses the given bytes as a Go object using the passed decoder.
    65  // Returns an error if the object cannot be parsed.
    66  func UnmarshalIPLD[T any](b []byte, decoder codec.Decoder, schema *Schema) (*T, error) {
    67  	t := new(T)
    68  	_, err := ipld.Unmarshal(b, decoder, t, schema.GetSchemaType(t))
    69  	return t, err
    70  }
    71  
    72  // Reinterpret re-parses the datamodel.Node as an object of the defined type.
    73  func Reinterpret[T any](node datamodel.Node, schema *Schema) (*T, error) {
    74  	// This is obviously slightly hacky and slow. but it is the most fool-proof
    75  	// way of doing this at time of writing, because go-ipld-prime cannot handle
    76  	// an algorithm using bindnode.Prototype and builder.AssignNode
    77  	schemaType := schema.GetSchemaType((*T)(nil))
    78  
    79  	var buf bytes.Buffer
    80  	var val = new(T)
    81  	err := json.Encode(node, &buf)
    82  	if err != nil {
    83  		return val, err
    84  	}
    85  
    86  	_, err = ipld.Unmarshal(buf.Bytes(), json.Decode, val, schemaType)
    87  	return val, err
    88  }
    89  
    90  // IPLD Maps are parsed by the ipld library into structures of this type rather
    91  // than just plain Go maps.
    92  type IPLDMap[K comparable, V any] struct {
    93  	Keys   []K
    94  	Values map[K]V
    95  }