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

Default value for enumeratum #1852

Merged
merged 14 commits into from
Feb 18, 2022
Merged

Default value for enumeratum #1852

merged 14 commits into from
Feb 18, 2022

Conversation

mkrzemien
Copy link
Contributor

Related to issue #1800.

In case of @default annotation the issue is challenging to resolve in general case. Here we lack direct access to codec for given type of request body (containing enum or other complex object).

In this PR I'd like to propose some work-aroungs.

First work-around is to add a second field to the @default annotation that would allow to explicitly provide the encoded value:

@default(E.Value, encoded=Some("Value"))

@mkrzemien mkrzemien requested a review from kubinio123 February 11, 2022 16:45
@mkrzemien mkrzemien self-assigned this Feb 11, 2022
@@ -79,7 +79,10 @@ object EndpointTransput {
schema(_.modifyUnsafe[U](Schema.ModifyCollectionElements)(_.validate(v)))

def description(d: String): ThisType[T] = copyWith(codec, info.description(d))
def default(d: T): ThisType[T] = copyWith(codec.schema(_.default(d, Some(codec.encode(d)))), info)
def default(d: T, encoded: Option[Any] = None): ThisType[T] = {
val e = Some(encoded.getOrElse(codec.encode(d)))
Copy link
Contributor

Choose a reason for hiding this comment

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

Should we allow to pass encoded here? It can cause ambiguity if someone passes a value that doest not match the codec encoding

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Without this change I get following compilation error:

[error] /home/mariusz/sml/tapir/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala:244:40: unknown parameter name: encoded
[error]     val derived = EndpointInput.derived[TapirRequestTest8].asInstanceOf[EndpointInput.Query[TapirRequestTest8]]
[error]                                        ^
[error] /home/mariusz/sml/tapir/core/src/test/scala/sttp/tapir/annotations/DeriveEndpointIOTest.scala:246:43: unknown parameter name: encoded
[error]     compareTransputs(EndpointInput.derived[TapirRequestTest8], expectedInput) shouldBe true
[error]                                           ^

Copy link
Contributor

Choose a reason for hiding this comment

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

Right, we are passing encoded using this method in annotations macro.

Copy link
Member

Choose a reason for hiding this comment

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

Encoded defaults only have to be passed to schemas, not to inputs (as they have codecs). So I guess @kubinio123 is right, and we should instead opt out of using the encoded parameter on inputs (and document why :) )

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I'll revert this change.
I just need to find another way to pass the encoded value from the @default annotation to the schema without using this method.

Copy link
Member

Choose a reason for hiding this comment

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

But the default value is already passed, being automatically encoded to the correct form?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It turned out that the EndpointIO.default method was used by the macros instead of the Schema.default. I've updated the macros and reverted the change in EndpointIO.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Reverting to using EndpointIO.default (as suggested in the review).
Adding more tests for macro handling annotation with additional parameter.

}

// #1800
test("use enum default if more than one") {
Copy link
Contributor

@kubinio123 kubinio123 Feb 13, 2022

Choose a reason for hiding this comment

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

How about "should use first specified default value"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

}

// #1800
test("ignore enum default in request body") {
Copy link
Contributor

@kubinio123 kubinio123 Feb 13, 2022

Choose a reason for hiding this comment

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

How about "should not add default when no encoded value specified"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

}

// #1800
test("add enum default in request body with given encoded") {
Copy link
Contributor

Choose a reason for hiding this comment

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

So it matches with case above - "should add default when encoded value specified"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Updated.

@kubinio123
Copy link
Contributor

Looks good ;) But the same has to be done for macro in scala3 - AnnotationsMacro and CaseClass

@mkrzemien mkrzemien assigned kubinio123 and unassigned mkrzemien Feb 14, 2022
@mkrzemien
Copy link
Contributor Author

I've updated Scala 3 macro.
Now I would like to add more test-cases for @default with different combinations of arguments.

@mkrzemien mkrzemien assigned mkrzemien and unassigned kubinio123 Feb 15, 2022
@mkrzemien mkrzemien marked this pull request as ready for review February 17, 2022 19:34
@kubinio123
Copy link
Contributor

@kubinio123 kubinio123 merged commit 89cdf06 into master Feb 18, 2022
@kubinio123 kubinio123 deleted the default_value_for_enumeratum branch February 18, 2022 11:12
@@ -86,6 +86,15 @@ class CaseClassField[Q <: Quotes, T](using val q: Q, t: Type[T])(
case _ => report.throwError(s"Cannot extract annotation: @${annSymbol.name}, from field: ${symbol.name}, of type: ${Type.show[T]}")
}

def extractTreeAndOptTreeFromAnnotation(annSymbol: Symbol): Option[(Tree, Tree)] = constructorField.getAnnotation(annSymbol).map {
case Apply(_, List(t, TypeApply(Select(_, "$lessinit$greater$default$2"), _))) => (t, '{ None }.asTerm)
Copy link
Member

Choose a reason for hiding this comment

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

out of curiosity, why not just return the second element of the list, which will correspond to the encoded annotation parameter?

Copy link
Contributor Author

@mkrzemien mkrzemien Feb 18, 2022

Choose a reason for hiding this comment

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

Currently this method is used in AnnotationMacros (and EndpointAnnotationMacros for Scala 2) where the first parameter is required.

Copy link
Member

Choose a reason for hiding this comment

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

Hm not sure I understand. Do you mean you needed to cover the cases when there are both 1 or 2 parameters if an annotation?

Copy link
Member

Choose a reason for hiding this comment

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

I think my question is, why wouldn't this work:

def extractTreeAndOptTreeFromAnnotation(annSymbol: Symbol): Option[(Tree, Tree)] = constructorField.getAnnotation(annSymbol).map {
  case Apply(_, List(t, u)) => (t, u)
}

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, I've added the method to cover the cases with 1 or 2 parameters. I've described all these cases in a new test-case.

Actually, only the first parameter is currently used, the second one being ignored. So as an alternative I've been considering a different method - returning first arg and simply ignoring the rest.

I've created another PR with this alternative. The patter matching there is indeed much simpler.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I think my question is, why wouldn't this work: (...)

I wanted to make sure, that the second parameter was Optional. Now I think I might have overcomplicated it :/

Copy link
Member

Choose a reason for hiding this comment

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

Happens :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] Default value for enumeratum enums is not added to OpenAPI schema
3 participants