diff options
| author | mo khan <mo@mokhan.ca> | 2025-05-11 21:12:57 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2025-05-11 21:12:57 -0600 |
| commit | 60440f90dca28e99a31dd328c5f6d5dc0f9b6a2e (patch) | |
| tree | 2f54adf55086516f162f0a55a5347e6b25f7f176 /vendor/github.com/google/jsonapi/request.go | |
| parent | 05ca9b8d3a9c7203a3a3b590beaa400900bd9007 (diff) | |
chore: vendor go dependencies
Diffstat (limited to 'vendor/github.com/google/jsonapi/request.go')
| -rw-r--r-- | vendor/github.com/google/jsonapi/request.go | 656 |
1 files changed, 656 insertions, 0 deletions
diff --git a/vendor/github.com/google/jsonapi/request.go b/vendor/github.com/google/jsonapi/request.go new file mode 100644 index 0000000..f665857 --- /dev/null +++ b/vendor/github.com/google/jsonapi/request.go @@ -0,0 +1,656 @@ +package jsonapi + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "reflect" + "strconv" + "strings" + "time" +) + +const ( + unsupportedStructTagMsg = "Unsupported jsonapi tag annotation, %s" +) + +var ( + // ErrInvalidTime is returned when a struct has a time.Time type field, but + // the JSON value was not a unix timestamp integer. + ErrInvalidTime = errors.New("Only numbers can be parsed as dates, unix timestamps") + // ErrInvalidISO8601 is returned when a struct has a time.Time type field and includes + // "iso8601" in the tag spec, but the JSON value was not an ISO8601 timestamp string. + ErrInvalidISO8601 = errors.New("Only strings can be parsed as dates, ISO8601 timestamps") + // ErrInvalidRFC3339 is returned when a struct has a time.Time type field and includes + // "rfc3339" in the tag spec, but the JSON value was not an RFC3339 timestamp string. + ErrInvalidRFC3339 = errors.New("Only strings can be parsed as dates, RFC3339 timestamps") + // ErrUnknownFieldNumberType is returned when the JSON value was a float + // (numeric) but the Struct field was a non numeric type (i.e. not int, uint, + // float, etc) + ErrUnknownFieldNumberType = errors.New("The struct field was not of a known number type") + // ErrInvalidType is returned when the given type is incompatible with the expected type. + ErrInvalidType = errors.New("Invalid type provided") // I wish we used punctuation. + +) + +// ErrUnsupportedPtrType is returned when the Struct field was a pointer but +// the JSON value was of a different type +type ErrUnsupportedPtrType struct { + rf reflect.Value + t reflect.Type + structField reflect.StructField +} + +func (eupt ErrUnsupportedPtrType) Error() string { + typeName := eupt.t.Elem().Name() + kind := eupt.t.Elem().Kind() + if kind.String() != "" && kind.String() != typeName { + typeName = fmt.Sprintf("%s (%s)", typeName, kind.String()) + } + return fmt.Sprintf( + "jsonapi: Can't unmarshal %+v (%s) to struct field `%s`, which is a pointer to `%s`", + eupt.rf, eupt.rf.Type().Kind(), eupt.structField.Name, typeName, + ) +} + +func newErrUnsupportedPtrType(rf reflect.Value, t reflect.Type, structField reflect.StructField) error { + return ErrUnsupportedPtrType{rf, t, structField} +} + +// UnmarshalPayload converts an io into a struct instance using jsonapi tags on +// struct fields. This method supports single request payloads only, at the +// moment. Bulk creates and updates are not supported yet. +// +// Will Unmarshal embedded and sideloaded payloads. The latter is only possible if the +// object graph is complete. That is, in the "relationships" data there are type and id, +// keys that correspond to records in the "included" array. +// +// For example you could pass it, in, req.Body and, model, a BlogPost +// struct instance to populate in an http handler, +// +// func CreateBlog(w http.ResponseWriter, r *http.Request) { +// blog := new(Blog) +// +// if err := jsonapi.UnmarshalPayload(r.Body, blog); err != nil { +// http.Error(w, err.Error(), 500) +// return +// } +// +// // ...do stuff with your blog... +// +// w.Header().Set("Content-Type", jsonapi.MediaType) +// w.WriteHeader(201) +// +// if err := jsonapi.MarshalPayload(w, blog); err != nil { +// http.Error(w, err.Error(), 500) +// } +// } +// +// +// Visit https://github.com/google/jsonapi#create for more info. +// +// model interface{} should be a pointer to a struct. +func UnmarshalPayload(in io.Reader, model interface{}) error { + payload := new(OnePayload) + + if err := json.NewDecoder(in).Decode(payload); err != nil { + return err + } + + if payload.Included != nil { + includedMap := make(map[string]*Node) + for _, included := range payload.Included { + key := fmt.Sprintf("%s,%s", included.Type, included.ID) + includedMap[key] = included + } + + return unmarshalNode(payload.Data, reflect.ValueOf(model), &includedMap) + } + return unmarshalNode(payload.Data, reflect.ValueOf(model), nil) +} + +// UnmarshalManyPayload converts an io into a set of struct instances using +// jsonapi tags on the type's struct fields. +func UnmarshalManyPayload(in io.Reader, t reflect.Type) ([]interface{}, error) { + payload := new(ManyPayload) + + if err := json.NewDecoder(in).Decode(payload); err != nil { + return nil, err + } + + models := []interface{}{} // will be populated from the "data" + includedMap := map[string]*Node{} // will be populate from the "included" + + if payload.Included != nil { + for _, included := range payload.Included { + key := fmt.Sprintf("%s,%s", included.Type, included.ID) + includedMap[key] = included + } + } + + for _, data := range payload.Data { + model := reflect.New(t.Elem()) + err := unmarshalNode(data, model, &includedMap) + if err != nil { + return nil, err + } + models = append(models, model.Interface()) + } + + return models, nil +} + +func unmarshalNode(data *Node, model reflect.Value, included *map[string]*Node) (err error) { + defer func() { + if r := recover(); r != nil { + err = fmt.Errorf("data is not a jsonapi representation of '%v'", model.Type()) + } + }() + + modelValue := model.Elem() + modelType := modelValue.Type() + + var er error + + for i := 0; i < modelValue.NumField(); i++ { + fieldType := modelType.Field(i) + tag := fieldType.Tag.Get("jsonapi") + if tag == "" { + continue + } + + fieldValue := modelValue.Field(i) + + args := strings.Split(tag, ",") + if len(args) < 1 { + er = ErrBadJSONAPIStructTag + break + } + + annotation := args[0] + + if (annotation == annotationClientID && len(args) != 1) || + (annotation != annotationClientID && len(args) < 2) { + er = ErrBadJSONAPIStructTag + break + } + + if annotation == annotationPrimary { + // Check the JSON API Type + if data.Type != args[1] { + er = fmt.Errorf( + "Trying to Unmarshal an object of type %#v, but %#v does not match", + data.Type, + args[1], + ) + break + } + + if data.ID == "" { + continue + } + + // ID will have to be transmitted as astring per the JSON API spec + v := reflect.ValueOf(data.ID) + + // Deal with PTRS + var kind reflect.Kind + if fieldValue.Kind() == reflect.Ptr { + kind = fieldType.Type.Elem().Kind() + } else { + kind = fieldType.Type.Kind() + } + + // Handle String case + if kind == reflect.String { + assign(fieldValue, v) + continue + } + + // Value was not a string... only other supported type was a numeric, + // which would have been sent as a float value. + floatValue, err := strconv.ParseFloat(data.ID, 64) + if err != nil { + // Could not convert the value in the "id" attr to a float + er = ErrBadJSONAPIID + break + } + + // Convert the numeric float to one of the supported ID numeric types + // (int[8,16,32,64] or uint[8,16,32,64]) + idValue, err := handleNumeric(floatValue, fieldType.Type, fieldValue) + if err != nil { + // We had a JSON float (numeric), but our field was not one of the + // allowed numeric types + er = ErrBadJSONAPIID + break + } + + assign(fieldValue, idValue) + } else if annotation == annotationClientID { + if data.ClientID == "" { + continue + } + + fieldValue.Set(reflect.ValueOf(data.ClientID)) + } else if annotation == annotationAttribute { + attributes := data.Attributes + + if attributes == nil || len(data.Attributes) == 0 { + continue + } + + attribute := attributes[args[1]] + + // continue if the attribute was not included in the request + if attribute == nil { + continue + } + + structField := fieldType + value, err := unmarshalAttribute(attribute, args, structField, fieldValue) + if err != nil { + er = err + break + } + + assign(fieldValue, value) + } else if annotation == annotationRelation { + isSlice := fieldValue.Type().Kind() == reflect.Slice + + if data.Relationships == nil || data.Relationships[args[1]] == nil { + continue + } + + if isSlice { + // to-many relationship + relationship := new(RelationshipManyNode) + + buf := bytes.NewBuffer(nil) + + json.NewEncoder(buf).Encode(data.Relationships[args[1]]) + json.NewDecoder(buf).Decode(relationship) + + data := relationship.Data + models := reflect.New(fieldValue.Type()).Elem() + + for _, n := range data { + m := reflect.New(fieldValue.Type().Elem().Elem()) + + if err := unmarshalNode( + fullNode(n, included), + m, + included, + ); err != nil { + er = err + break + } + + models = reflect.Append(models, m) + } + + fieldValue.Set(models) + } else { + // to-one relationships + relationship := new(RelationshipOneNode) + + buf := bytes.NewBuffer(nil) + + json.NewEncoder(buf).Encode( + data.Relationships[args[1]], + ) + json.NewDecoder(buf).Decode(relationship) + + /* + http://jsonapi.org/format/#document-resource-object-relationships + http://jsonapi.org/format/#document-resource-object-linkage + relationship can have a data node set to null (e.g. to disassociate the relationship) + so unmarshal and set fieldValue only if data obj is not null + */ + if relationship.Data == nil { + continue + } + + m := reflect.New(fieldValue.Type().Elem()) + if err := unmarshalNode( + fullNode(relationship.Data, included), + m, + included, + ); err != nil { + er = err + break + } + + fieldValue.Set(m) + + } + + } else { + er = fmt.Errorf(unsupportedStructTagMsg, annotation) + } + } + + return er +} + +func fullNode(n *Node, included *map[string]*Node) *Node { + includedKey := fmt.Sprintf("%s,%s", n.Type, n.ID) + + if included != nil && (*included)[includedKey] != nil { + return (*included)[includedKey] + } + + return n +} + +// assign will take the value specified and assign it to the field; if +// field is expecting a ptr assign will assign a ptr. +func assign(field, value reflect.Value) { + value = reflect.Indirect(value) + + if field.Kind() == reflect.Ptr { + // initialize pointer so it's value + // can be set by assignValue + field.Set(reflect.New(field.Type().Elem())) + field = field.Elem() + + } + + assignValue(field, value) +} + +// assign assigns the specified value to the field, +// expecting both values not to be pointer types. +func assignValue(field, value reflect.Value) { + switch field.Kind() { + case reflect.Int, reflect.Int8, reflect.Int16, + reflect.Int32, reflect.Int64: + field.SetInt(value.Int()) + case reflect.Uint, reflect.Uint8, reflect.Uint16, + reflect.Uint32, reflect.Uint64, reflect.Uintptr: + field.SetUint(value.Uint()) + case reflect.Float32, reflect.Float64: + field.SetFloat(value.Float()) + case reflect.String: + field.SetString(value.String()) + case reflect.Bool: + field.SetBool(value.Bool()) + default: + field.Set(value) + } +} + +func unmarshalAttribute( + attribute interface{}, + args []string, + structField reflect.StructField, + fieldValue reflect.Value) (value reflect.Value, err error) { + value = reflect.ValueOf(attribute) + fieldType := structField.Type + + // Handle field of type []string + if fieldValue.Type() == reflect.TypeOf([]string{}) { + value, err = handleStringSlice(attribute) + return + } + + // Handle field of type time.Time + if fieldValue.Type() == reflect.TypeOf(time.Time{}) || + fieldValue.Type() == reflect.TypeOf(new(time.Time)) { + value, err = handleTime(attribute, args, fieldValue) + return + } + + // Handle field of type struct + if fieldValue.Type().Kind() == reflect.Struct { + value, err = handleStruct(attribute, fieldValue) + return + } + + // Handle field containing slice of structs + if fieldValue.Type().Kind() == reflect.Slice && + reflect.TypeOf(fieldValue.Interface()).Elem().Kind() == reflect.Struct { + value, err = handleStructSlice(attribute, fieldValue) + return + } + + // JSON value was a float (numeric) + if value.Kind() == reflect.Float64 { + value, err = handleNumeric(attribute, fieldType, fieldValue) + return + } + + // Field was a Pointer type + if fieldValue.Kind() == reflect.Ptr { + value, err = handlePointer(attribute, args, fieldType, fieldValue, structField) + return + } + + // As a final catch-all, ensure types line up to avoid a runtime panic. + if fieldValue.Kind() != value.Kind() { + err = ErrInvalidType + return + } + + return +} + +func handleStringSlice(attribute interface{}) (reflect.Value, error) { + v := reflect.ValueOf(attribute) + values := make([]string, v.Len()) + for i := 0; i < v.Len(); i++ { + values[i] = v.Index(i).Interface().(string) + } + + return reflect.ValueOf(values), nil +} + +func handleTime(attribute interface{}, args []string, fieldValue reflect.Value) (reflect.Value, error) { + var isISO8601, isRFC3339 bool + v := reflect.ValueOf(attribute) + + if len(args) > 2 { + for _, arg := range args[2:] { + if arg == annotationISO8601 { + isISO8601 = true + } else if arg == annotationRFC3339 { + isRFC3339 = true + } + } + } + + if isISO8601 { + if v.Kind() != reflect.String { + return reflect.ValueOf(time.Now()), ErrInvalidISO8601 + } + + t, err := time.Parse(iso8601TimeFormat, v.Interface().(string)) + if err != nil { + return reflect.ValueOf(time.Now()), ErrInvalidISO8601 + } + + if fieldValue.Kind() == reflect.Ptr { + return reflect.ValueOf(&t), nil + } + + return reflect.ValueOf(t), nil + } + + if isRFC3339 { + if v.Kind() != reflect.String { + return reflect.ValueOf(time.Now()), ErrInvalidRFC3339 + } + + t, err := time.Parse(time.RFC3339, v.Interface().(string)) + if err != nil { + return reflect.ValueOf(time.Now()), ErrInvalidRFC3339 + } + + if fieldValue.Kind() == reflect.Ptr { + return reflect.ValueOf(&t), nil + } + + return reflect.ValueOf(t), nil + } + + var at int64 + + if v.Kind() == reflect.Float64 { + at = int64(v.Interface().(float64)) + } else if v.Kind() == reflect.Int { + at = v.Int() + } else { + return reflect.ValueOf(time.Now()), ErrInvalidTime + } + + t := time.Unix(at, 0) + + return reflect.ValueOf(t), nil +} + +func handleNumeric( + attribute interface{}, + fieldType reflect.Type, + fieldValue reflect.Value) (reflect.Value, error) { + v := reflect.ValueOf(attribute) + floatValue := v.Interface().(float64) + + var kind reflect.Kind + if fieldValue.Kind() == reflect.Ptr { + kind = fieldType.Elem().Kind() + } else { + kind = fieldType.Kind() + } + + var numericValue reflect.Value + + switch kind { + case reflect.Int: + n := int(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int8: + n := int8(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int16: + n := int16(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int32: + n := int32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Int64: + n := int64(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint: + n := uint(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint8: + n := uint8(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint16: + n := uint16(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint32: + n := uint32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Uint64: + n := uint64(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Float32: + n := float32(floatValue) + numericValue = reflect.ValueOf(&n) + case reflect.Float64: + n := floatValue + numericValue = reflect.ValueOf(&n) + default: + return reflect.Value{}, ErrUnknownFieldNumberType + } + + return numericValue, nil +} + +func handlePointer( + attribute interface{}, + args []string, + fieldType reflect.Type, + fieldValue reflect.Value, + structField reflect.StructField) (reflect.Value, error) { + t := fieldValue.Type() + var concreteVal reflect.Value + + switch cVal := attribute.(type) { + case string: + concreteVal = reflect.ValueOf(&cVal) + case bool: + concreteVal = reflect.ValueOf(&cVal) + case complex64, complex128, uintptr: + concreteVal = reflect.ValueOf(&cVal) + case map[string]interface{}: + var err error + concreteVal, err = handleStruct(attribute, fieldValue) + if err != nil { + return reflect.Value{}, newErrUnsupportedPtrType( + reflect.ValueOf(attribute), fieldType, structField) + } + return concreteVal, err + default: + return reflect.Value{}, newErrUnsupportedPtrType( + reflect.ValueOf(attribute), fieldType, structField) + } + + if t != concreteVal.Type() { + return reflect.Value{}, newErrUnsupportedPtrType( + reflect.ValueOf(attribute), fieldType, structField) + } + + return concreteVal, nil +} + +func handleStruct( + attribute interface{}, + fieldValue reflect.Value) (reflect.Value, error) { + + data, err := json.Marshal(attribute) + if err != nil { + return reflect.Value{}, err + } + + node := new(Node) + if err := json.Unmarshal(data, &node.Attributes); err != nil { + return reflect.Value{}, err + } + + var model reflect.Value + if fieldValue.Kind() == reflect.Ptr { + model = reflect.New(fieldValue.Type().Elem()) + } else { + model = reflect.New(fieldValue.Type()) + } + + if err := unmarshalNode(node, model, nil); err != nil { + return reflect.Value{}, err + } + + return model, nil +} + +func handleStructSlice( + attribute interface{}, + fieldValue reflect.Value) (reflect.Value, error) { + models := reflect.New(fieldValue.Type()).Elem() + dataMap := reflect.ValueOf(attribute).Interface().([]interface{}) + for _, data := range dataMap { + model := reflect.New(fieldValue.Type().Elem()).Elem() + + value, err := handleStruct(data, model) + + if err != nil { + continue + } + + models = reflect.Append(models, reflect.Indirect(value)) + } + + return models, nil +} |
