-
Notifications
You must be signed in to change notification settings - Fork 76
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
Update realm and schema module to rely on cats-effect #4194
Conversation
a00b83d
to
02ca26d
Compare
@@ -49,6 +50,13 @@ class RealmsRoutes(identities: Identities, realms: Realms, aclCheck: AclCheck)(i | |||
RealmSearchParams(None, deprecated, rev, createdBy, updatedBy) | |||
} | |||
|
|||
private def emitFetch(io: IO[RealmResource]): Route = emit(io.attemptNarrow[RealmRejection]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.attemptNarrow
allows to fallback to a IO[Either[RealmRejection, A]]
|
||
import scala.reflect.ClassTag | ||
|
||
trait MigrateEffectSyntax { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Allow to go to BIO to IO and IO to BIO
|
||
import scala.concurrent.duration._ | ||
|
||
object KamonMonitoringCats { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
KamonMonitoring adapted to CE IO
import scala.io.{Codec, Source} | ||
import scala.jdk.CollectionConverters._ | ||
|
||
trait CatsEffectsClasspathResourceUtils { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ClasspathResourceUtils for CE IO
import java.nio.charset.StandardCharsets | ||
import java.util.Base64 | ||
|
||
sealed trait CatsResponseToJsonLd { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To serialize both success and error channels in the API
Adapted from ResponseToJsonLd
|
||
object DeltaDirectives extends DeltaDirectives | ||
|
||
trait DeltaDirectives extends UriDirectives { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Delta directives for CE IO
private def openIdAlreadyExists(uri: Uri) = | ||
(_: Label, _: Uri) => IO.raiseError(RealmOpenIdConfigAlreadyExists(label, uri)) | ||
|
||
group("Evaluate a create command") { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Idea copied from fs2, the output looks like this:
ch.epfl.bluebrain.nexus.delta.sdk.schemas.SchemasImplSuite:
+ Creating a schema - Succeed with the id present in the payload 0.829s
+ Creating a schema - Fail with different ids on the payload and passed 0.008s
+ Updating a schema - Succeed 0.042s
+ Updating a schema - Fail if the schema does not exist 0.009s
+ Refreshing a schema - succeed 0.037s
+ Refreshing a schema - Fail if it does not exist 0.006s
import scala.reflect.ClassTag | ||
import scala.util.control.NonFatal | ||
|
||
trait CatsEffectAssertions { self: Assertions => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Taken from munit cats-effects
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am missing what is blocking us from just using munit-cats-effect
directly?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not maintained by CE2 and it relies on a outdated version of MUnit
import fs2.Stream | ||
import monix.bio.{Task, UIO} | ||
|
||
import java.time.Instant | ||
import java.util.UUID | ||
|
||
class ElemRoutesSpec extends BaseRouteSpec with CirceLiteral { | ||
class ElemRoutesSpec extends BaseRouteSpec with CirceLiteral with IOFromMap { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Temporary, we can change this back later
(parameter("rev".as[Int].?) & pathEndOrSingleSlash & entity(as[Json])) { | ||
case (None, source) => | ||
// Create a schema with id segment | ||
emitMetadata(Created, schemas.create(id, ref, source).flatTap(index)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is much clearer, thanks
operationName(s"$prefixSegment/schemas/{org}/{project}/{id}/source") { | ||
authorizeFor(ref, Read).apply { | ||
implicit val source: Printer = sourcePrinter | ||
val sourceIO = schemas.fetch(id, ref).map(_.value.source) | ||
emit(sourceIO.leftWiden[SchemaRejection].rejectOn[SchemaNotFound]) | ||
} | ||
authorizeFor(ref, Read).apply { | ||
emitSource(schemas.fetch(id, ref)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's really nice to hide this printer boilerplate
private def emitTags(io: IO[SchemaResource]): Route = | ||
emit(io.map(_.value.tags).attemptNarrow[SchemaRejection].rejectOn[SchemaNotFound]) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't love the rejectOn
being hidden here, although it's probably just nicer if we removed rejectOn :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It felt better than having it separated from attemptNarrow
And we can't get rid of the rejectOn
for now
@@ -17,6 +17,15 @@ trait IOUtils { | |||
clock.realTime(TimeUnit.MILLISECONDS).map(Instant.ofEpochMilli) | |||
} | |||
|
|||
object IOUtils extends IOUtils |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider calling Clock.now
or similar, the important thing here is that you're getting the current time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IOUtils
is gonna be replaced with IOInstant
progressively
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, but IOInstant.get
is less readable than something like now
or Clock.now
where the intention is clearer
If you imported get
then you would have to rename it everywhere, so calling it now
instead of get helps
// format: off | ||
def create(c: CreateRealm) = | ||
state.fold { | ||
openIdExists(c.label, c.openIdConfig) >> (wellKnown(c.openIdConfig), IOUtils.instant).mapN { | ||
openIdExists(c.label, c.openIdConfig) >> (wellKnown(c.openIdConfig), IOInstant.get).mapN { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be just now
with the rename I suggested earlier and an import of the method
group("Fail with realm not found") { | ||
List( | ||
None -> UpdateRealm(label, 1, name, wellKnownUri, None, None, subject), | ||
None -> DeprecateRealm(label, 1, subject) | ||
).foreach { case (state, cmd) => | ||
test(s"for a ${cmd.getClass.getSimpleName} command when the state does not exist") { | ||
evaluate(wkResolution, (_, _) => IO.unit)(state, cmd).intercept[RealmNotFound] | ||
} | ||
} | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This isn't a group
|
||
abstract class Rejection extends Exception with Product with Serializable { | ||
|
||
def reason: String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think the message in exception should probably be this reason, perhaps even reason should be an argument. It's definitely worth thinking about this now before everything is using it and it's hard to change
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The reason
is currently brought up all the way to the API to serialize the errors so it is difficult to change reason
for message
protected def group(name: String)(thunk: => Unit): Unit = { | ||
val countBefore = munitTestsBuffer.size | ||
val _ = thunk | ||
val countAfter = munitTestsBuffer.size | ||
val countRegistered = countAfter - countBefore | ||
val registered = munitTestsBuffer.toList.drop(countBefore) | ||
(0 until countRegistered).foreach(_ => munitTestsBuffer.remove(countBefore)) | ||
registered.foreach(t => munitTestsBuffer += t.withName(s"$name - ${t.name}")) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would just want to know that individual tests can still run in intellij easily? As in, click the test name and then run
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it's possible. It's also not possible to run a group like this. And to run a single test (from the CLI for instance) you need to specify "{groupName} - {testName}" to run an individual test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I never use this feature maybe with a munit extension it does
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible to run a single normal munit test, I just need that to still be possible
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def assertSome(value: A): Unit = | ||
assert(option.contains(value), s"$value was not found in the option, it contains '${option.getOrElse("None")}'") | ||
def assertNone(): Unit = | ||
assert( | ||
option.isEmpty, | ||
s"The option is not empty, it contains ${option.getOrElse(new IllegalStateException("Should not happen))}."))}" | ||
) | ||
|
||
def assertSome(): Unit = | ||
assert(option.isDefined, s"The option is empty.") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't know if I like these methods. It feels like these failure messages won't be helpful
No description provided.