Skip to content

Commit

Permalink
Fix unmarshalling for courses (#197)
Browse files Browse the repository at this point in the history
Created custom unmarshaller for collection requirements.
  • Loading branch information
neboman11 authored Nov 17, 2023
1 parent 7539fac commit f1b8c6b
Show file tree
Hide file tree
Showing 2 changed files with 148 additions and 33 deletions.
37 changes: 31 additions & 6 deletions api/schema/objects.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,49 @@ package schema

import (
"encoding/json"
"reflect"
"time"

"go.mongodb.org/mongo-driver/bson/primitive"
"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/bsoncodec"
"go.mongodb.org/mongo-driver/bson/bsonrw"
)

// Wrapper type for primitive.ObjectID to allow for custom mashalling below
type IdWrapper struct {
primitive.ObjectID
}
type IdWrapper string

// Custom JSON marshalling for ObjectID to marshal ObjectIDs correctly
func (id IdWrapper) MarshalJSON() (data []byte, err error) {

type tmp struct {
Id primitive.ObjectID `json:"$oid"`
Id string `json:"$oid"`
}

return json.Marshal(tmp{id.ObjectID})
return json.Marshal(tmp{string(id)})
}

func CreateCustomRegistry() *bsoncodec.RegistryBuilder {
var primitiveCodecs bson.PrimitiveCodecs
rb := bsoncodec.NewRegistryBuilder()
bsoncodec.DefaultValueEncoders{}.RegisterDefaultEncoders(rb)
bsoncodec.DefaultValueDecoders{}.RegisterDefaultDecoders(rb)
// register our new type
idWrapperType := reflect.TypeOf(IdWrapper(""))
// read the datetime type and convert to integer
rb.RegisterTypeDecoder(
idWrapperType,
bsoncodec.ValueDecoderFunc(func(_ bsoncodec.DecodeContext, vr bsonrw.ValueReader, val reflect.Value) error {
// this is the function when we read the datetime format
read, err := vr.ReadObjectID()
if err != nil {
return err
}
val.SetString(read.Hex())
return nil
}),
)
primitiveCodecs.RegisterPrimitiveCodecs(rb)
return rb
}

type Course struct {
Expand Down
144 changes: 117 additions & 27 deletions api/schema/requirements.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
package schema

import (
"fmt"

"go.mongodb.org/mongo-driver/bson"
"go.mongodb.org/mongo-driver/bson/primitive"
)

Expand All @@ -9,7 +12,7 @@ type Requirement struct {
}

type CourseRequirement struct {
Requirement
Requirement `bson:",inline" json:",inline"`
ClassReference string `bson:"class_reference" json:"class_reference"`
MinimumGrade string `bson:"minimum_grade" json:"minimum_grade"`
}
Expand All @@ -19,7 +22,7 @@ func NewCourseRequirement(classRef string, minGrade string) *CourseRequirement {
}

type SectionRequirement struct {
Requirement
Requirement `bson:",inline" json:",inline"`
SectionReference primitive.ObjectID `bson:"section_reference" json:"section_reference"`
}

Expand All @@ -28,7 +31,7 @@ func NewSectionRequirement(sectionRef primitive.ObjectID) *SectionRequirement {
}

type ExamRequirement struct {
Requirement
Requirement `bson:",inline" json:",inline"`
ExamReference string `bson:"exam_reference" json:"exam_reference"`
MinimumScore float64 `bson:"minimum_score" json:"minimum_score"`
}
Expand All @@ -38,44 +41,44 @@ func NewExamRequirement(examRef string, minScore float64) *ExamRequirement {
}

type MajorRequirement struct {
Requirement
Major string `bson:"major" json:"major"`
Requirement `bson:",inline" json:",inline"`
Major string `bson:"major" json:"major"`
}

func NewMajorRequirement(major string) *MajorRequirement {
return &MajorRequirement{Requirement{"major"}, major}
}

type MinorRequirement struct {
Requirement
Minor string `bson:"minor" json:"minor"`
Requirement `bson:",inline" json:",inline"`
Minor string `bson:"minor" json:"minor"`
}

func NewMinorRequirement(minor string) *MinorRequirement {
return &MinorRequirement{Requirement{"minor"}, minor}
}

type GPARequirement struct {
Requirement
Minimum float64 `bson:"minimum" json:"minimum"`
Subset string `bson:"subset" json:"subset"`
Requirement `bson:",inline" json:",inline"`
Minimum float64 `bson:"minimum" json:"minimum"`
Subset string `bson:"subset" json:"subset"`
}

func NewGPARequirement(min float64, subset string) *GPARequirement {
return &GPARequirement{Requirement{"gpa"}, min, subset}
}

type ConsentRequirement struct {
Requirement
Granter string `bson:"granter" json:"granter"`
Requirement `bson:",inline" json:",inline"`
Granter string `bson:"granter" json:"granter"`
}

func NewConsentRequirement(granter string) *ConsentRequirement {
return &ConsentRequirement{Requirement{"consent"}, granter}
}

type OtherRequirement struct {
Requirement
Requirement `bson:",inline" json:",inline"`
Description string `bson:"description" json:"description"`
Condition string `bson:"condition" json:"condition"`
}
Expand All @@ -85,48 +88,135 @@ func NewOtherRequirement(description, condition string) *OtherRequirement {
}

type CollectionRequirement struct {
Requirement
Name string `bson:"name" json:"name"`
Required int `bson:"required" json:"required"`
Options []interface{} `bson:"options" json:"options"`
Requirement `bson:",inline" json:",inline"`
Name string `bson:"name" json:"name"`
Required int `bson:"required" json:"required"`
Options []interface{} `bson:"options" json:"options"`
}

type CollectionRequirementIntermediate struct {
Name string `bson:"name"`
Required int `bson:"required"`
Options []bson.Raw `bson:"options"`
}

func NewCollectionRequirement(name string, required int, options []interface{}) *CollectionRequirement {
return &CollectionRequirement{Requirement{"collection"}, name, required, options}
}

func (cr *CollectionRequirement) UnmarshalBSON(data []byte) error {
var dummyCollection CollectionRequirementIntermediate
err := bson.Unmarshal(data, &dummyCollection)
if err != nil {
return err
}

var out []interface{}
for _, v := range dummyCollection.Options {

bytes, err := bson.Marshal(v)
if err != nil {
return err
}

optionType := v.Lookup("type").StringValue()

switch optionType {
case "course":
var t CourseRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "section":
var t SectionRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "exam":
var t ExamRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "major":
var t MajorRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "minor":
var t MinorRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "gpa":
var t GPARequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "consent":
var t ConsentRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "collection":
var t CollectionRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "hours":
var t HoursRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "other":
var t OtherRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "choice":
var t ChoiceRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "limit":
var t LimitRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
case "core":
var t CoreRequirement
bson.Unmarshal(bytes, &t)
out = append(out, t)
default:
return fmt.Errorf("unknown option type: %v", err)
}
}
cr.Name = dummyCollection.Name
cr.Required = dummyCollection.Required
cr.Options = out
cr.Type = "collection"
return nil
}

type HoursRequirement struct {
Requirement
Required int `bson:"required" json:"required"`
Options []*CourseRequirement `bson:"options" json:"options"`
Requirement `bson:",inline" json:",inline"`
Required int `bson:"required" json:"required"`
Options []*CourseRequirement `bson:"options" json:"options"`
}

func NewHoursRequirement(required int, options []*CourseRequirement) *HoursRequirement {
return &HoursRequirement{Requirement{"hours"}, required, options}
}

type ChoiceRequirement struct {
Requirement
Choices *CollectionRequirement `bson:"choices" json:"choices"`
Requirement `bson:",inline" json:",inline"`
Choices *CollectionRequirement `bson:"choices" json:"choices"`
}

func NewChoiceRequirement(choices *CollectionRequirement) *ChoiceRequirement {
return &ChoiceRequirement{Requirement{"choice"}, choices}
}

type LimitRequirement struct {
Requirement
MaxHours int `bson:"max_hours" json:"max_hours"`
Requirement `bson:",inline" json:",inline"`
MaxHours int `bson:"max_hours" json:"max_hours"`
}

func NewLimitRequirement(maxHours int) *LimitRequirement {
return &LimitRequirement{Requirement{"limit"}, maxHours}
}

type CoreRequirement struct {
Requirement
CoreFlag string `bson:"core_flag" json:"core_flag"`
Hours int `bson:"hours" json:"hours"`
Requirement `bson:",inline" json:",inline"`
CoreFlag string `bson:"core_flag" json:"core_flag"`
Hours int `bson:"hours" json:"hours"`
}

func NewCoreRequirement(coreFlag string, hours int) *CoreRequirement {
Expand Down

0 comments on commit f1b8c6b

Please sign in to comment.