Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

An alternate approach to enums #930

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ enum ObsAttachmentType { DUMMY }
enum FilterType { DUMMY }
enum ProposalAttachmentType { DUMMY }
enum ProposalStatus { DUMMY }
enum ToOActivation { DUMMY }

"DatasetEvent creation parameters."
input AddDatasetEventInput {
Expand Down Expand Up @@ -9785,25 +9786,25 @@ type TimestampInterval {

}

"""
ToO Activation
"""
enum ToOActivation {
"""
ToOActivation None
"""
NONE
# """
# ToO Activation
# """
# enum ToOActivation {
# """
# ToOActivation None
# """
# NONE

"""
ToOActivation Standard
"""
STANDARD
# """
# ToOActivation Standard
# """
# STANDARD

"""
ToOActivation Rapid
"""
RAPID
}
# """
# ToOActivation Rapid
# """
# RAPID
# }

input UnlinkUserInput {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,26 @@
package lucuma.odb.graphql.enums

import cats.Monad
import cats.MonadThrow
import cats.data.NonEmptyList
import cats.syntax.flatMap.*
import cats.syntax.functor.*
import cats.syntax.traverse.*
import cats.syntax.all.*
import grackle.DirectiveDef
import grackle.EnumType
import grackle.NamedType
import grackle.Schema
import grackle.SchemaExtension
import grackle.TypeExtension
import lucuma.core.enums.Instrument
import lucuma.core.enums.ToOActivation
import lucuma.core.util.Enumerated
import lucuma.core.util.TimeSpan
import org.tpolecat.sourcepos.SourcePos
import org.typelevel.log4cats.Logger
import skunk.Codec
import skunk.Fragment
import skunk.Session
import skunk.data.Arr
import skunk.data.Type

/**
* Enums loaded from the database on startup. These fall into two categories:
Expand Down Expand Up @@ -113,7 +117,7 @@ final class Enums(
def pos: SourcePos = SourcePos.instance
def baseTypes: List[NamedType] =
Enumerated[ProposalStatus].toEnumType("ProposalStatus", "Enumerated type of ProposalStatus")(_.name) ::
enumMeta.unreferencedTypes
enumMeta.unreferencedTypes ++ enumMeta.scalaEnumTypes
def directives: List[DirectiveDef] = Nil
def schemaExtensions: List[SchemaExtension] = Nil
def typeExtensions: List[TypeExtension] = Nil
Expand All @@ -122,15 +126,27 @@ final class Enums(
}

object Enums {
case class ScalaEnum(
postgresName: String,
enumType: EnumType
)

// enums defined in scala for which we want to validate the postgres enums and add to the schema
val scalaEnums: List[ScalaEnum] =
List(
ScalaEnum("e_too_activation", Enumerated[ToOActivation].toEnumType("ToOActivation", "Target of Opportunity Activation")(_.label))
Copy link
Contributor Author

@toddburnside toddburnside Jan 22, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The description string ("Target of Opportunity Activiation") doesn't seem to be actually be used for anything? It would be nice if it could be the comment for the enum itself in the schema.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

However, we can add comments before the DUMMY entries in OdbSchema.graphql and they will be retained in the final schema.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What happens if the validation with postgres fails?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The service doesn't start. Same thing as if there is a problem loading the dynamic enums.

)

case class Meta(
timeEstimate: Map[String, TimeEstimateMeta],
proposalStatus: Map[(String, Short), ProposalStatusMeta],
unreferencedTypes: List[EnumType]
unreferencedTypes: List[EnumType],
scalaEnumTypes: List[EnumType]
)

def load[F[_]: Monad: Logger](s: Session[F]): F[Enums] =
def load[F[_]: MonadThrow: Logger](s: Session[F]): F[Enums] =
for {
_ <- scalaEnums.traverse(se => validateScalaEnums(s, se))
te <- TimeEstimateMeta.select(s)
ps <- ProposalStatusMeta.select(s)
un <- List(
Expand All @@ -144,6 +160,19 @@ object Enums {
ConditionsMeasurementSourceEnumType.fetch(s),
SeeingTrendEnumType.fetch(s),
).sequence
} yield Enums(Meta(te, ps, un))
} yield Enums(Meta(te, ps, un, scalaEnums.map(_.enumType)))

def validateScalaEnums[F[_]: MonadThrow: Logger](s: Session[F], scalaEnum: ScalaEnum): F[Unit] =
val _text_enum: Codec[List[String]] =
Codec.array[String](_.toString, _.asRight[String], Type(s"_${scalaEnum.postgresName}", List(Type(scalaEnum.postgresName))))
.imap(_.toList)(l => Arr.fromFoldable(l))
// This is probably abusing skunk - is there a better way?
val query = Fragment(List(Left(s"select enum_range(null::${scalaEnum.postgresName})")), skunk.Void.codec, skunk.util.Origin.unknown)
val scalaValues = scalaEnum.enumType.enumValues.map(_.name.toLowerCase)
s.unique(query.query(_text_enum)).flatMap(l =>
// May need to allow for different ordering and conversions
if (l === scalaValues) Monad[F].unit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

raiseUnless ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice.

else MonadThrow[F].raiseError(Exception(s"Enumeration ${scalaEnum.postgresName} with values $l does not match Scala Enumerated with values $scalaValues"))
)

}