code: func (c *Connector) Read(ctx context.Context, ei *dosa.EntityInfo, keys map[string]dosa.FieldValue, minimumFields []string) (map[string]dosa.FieldValue, error) {
  	// Convert the fields from the client's map to a set of fields to read
  	var rpcMinimumFields map[string]struct{}
  	if minimumFields != nil {
  		rpcMinimumFields = map[string]struct{}{}
  		for _, field := range minimumFields {
  			rpcMinimumFields[field] = struct{}{}
  		}
  	}
  
  	// convert the key values from interface{} to RPC's Value
  	rpcFields := make(dosarpc.FieldValueMap)
  	for key, value := range keys {
  		rv, err := RawValueFromInterface(value)
  		if err != nil {
  			return nil, errors.Wrapf(err, "Key field %q", key)
  		}
  		if rv == nil {
  			continue
  		}
  		rpcValue := &dosarpc.Value{ElemValue: rv}
  		rpcFields[key] = rpcValue
  	}
  
  	// perform the read request
  	readRequest := &dosarpc.ReadRequest{
  		Ref:          entityInfoToSchemaRef(ei),
  		KeyValues:    rpcFields,
  		FieldsToRead: rpcMinimumFields,
  	}
  
  	response, err := c.client.Read(ctx, readRequest, getHeaders(c.headers)...)
  	if err != nil {
  		if be, ok := err.(*dosarpc.BadRequestError); ok {
  			if be.ErrorCode != nil && *be.ErrorCode == errCodeNotFound {
  				return nil, errors.Wrap(&dosa.ErrNotFound{}, "Read failed: not found")
  			}
  		}
  
  		if !dosarpc.Dosa_Read_Helper.IsException(err) {
  			return nil, errors.Wrap(err, "failed to Read due to network issue")
  		}
  
  		return nil, errors.Wrap(err, "failed to Read")
  	}
  
  	// no error, so for each column, transform it into the map of (col->value) items
  
  	return decodeResults(ei, response.EntityValues), nil
  }
docs: // Read reads a single entity