Where communities thrive


  • Join over 1.5M+ people
  • Join over 100K+ communities
  • Free without limits
  • Create your own community
People
Repo info
Activity
  • Dec 01 08:19
    sammy-da commented #1647
  • Dec 01 08:18
    sammy-da commented #1647
  • Dec 01 08:17
    sammy-da commented #1647
  • Dec 01 08:16
    sammy-da commented #1647
  • Dec 01 08:06
    sammy-da commented #1647
  • Dec 01 08:05
    sammy-da commented #1647
  • Dec 01 07:18
    blast-hardcheese commented #1647
  • Nov 30 03:28
    sammy-da edited #1647
  • Nov 30 03:27
    sammy-da edited #1647
  • Nov 30 03:26
    sammy-da edited #1647
  • Nov 29 17:40
    sammy-da opened #1647
  • Nov 29 15:46
    ArinRayAtCornerStoneFS commented #195
  • Nov 29 15:45
    ArinRayAtCornerStoneFS commented #195
  • Nov 28 16:46
    github-actions[bot] labeled #1646
  • Nov 28 16:46
    github-actions[bot] labeled #1646
  • Nov 28 16:40
    scala-steward opened #1646
  • Nov 26 16:41
    github-actions[bot] labeled #1645
  • Nov 26 16:30
    scala-steward closed #1636
  • Nov 26 16:30
    scala-steward commented #1636
  • Nov 26 16:30
    scala-steward opened #1645
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
otoh we could barf at mount time if we recognize we don't have a module registered
again not a great idea
but technically possible.
kelnos
@kelnos:matrix.org
[m]
hmmm
not really; we don't have access to the ObjectMapper from the route class
(tho i wonder if DW lets you @Inject one into the class...)
i kinda do like the idea of setting up the POJOs with annotations that specify the "server (de)serializer", and that will just work, but in the client i can just pass a custom (de)serializer when calling the objectmapper
i think you can do that. not 100% sure. but i think so.
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
I don't super like the idea of "default client unless known server", since any break in the chain switches semantics
kelnos
@kelnos:matrix.org
[m]
it's not great, but i don't see a better way to do it
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
yeah
(also, I read your comment backwards -- my objection still stands, but I at least better understand what you mean)
kelnos
@kelnos:matrix.org
[m]
right. it's default server unless known client
kelnos
@kelnos:matrix.org
[m]
there's also perhaps a question around behavior of required=true,nullable=false properties when a default is present. like, i could see the argument that a client could actually leave that property out entirely, because the server should know about the default, and automatically fall back (so these sorts of properties automatically get 'demoted' to required=false,nullable=false). but maybe you want to think of it as more of a "UX default": the server will barf if you don't include it, and the default there is mainly to make it so the user of the client doesn't have to explicitly fill in the property (because the client can do it for them).
(my preference is for the latter behavior, to be clear)
blast_hardcheese
@blast_hardcheese:matrix.org
[m]

At the risk of making the wrong gut decision here, but required=true,nullable=false with a default should have the default injected by the server before the user sees it, regardless of presence in the actual wire encoding. If that means translating that into required=false,nullable=false then so be it.

The expectation would then be that removing the default from the spec would cause the generated code to change and a compiler error to be emitted.

kelnos
@kelnos:matrix.org
[m]
oh agreed. but i'm talking about client requests there, not server responses
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
I think this would give the best experience for consumers -- smaller wire encoding, server-enforced defaults, and no trickery about secret defaults smuggled around in builders
kelnos
@kelnos:matrix.org
[m]

i'm still struggling a bit, though. the more i think about it... well, take the most "flexible" case, required=false,nullable=true. that's our tri-state of absent, null, or present. if i think about reflecting the intent of the user of the client, i would say that leaving things as absent would mean the client is saying "i don't care", and the server should assume the default value. if the client explicitly sends null, then i would think the client is saying "really, i mean no value here", and the server should not substitute the default.

if we accept that as reasonable -- maybe we won't, but let's assume we will, then what does that mean for the other two non-trivial cases?

required=false,nullable=false with a default still makes sense. if the client leaves the field empty, that still sounds like the "i don't care" case, and the server should substitute the default value.

but does required=true,nullable=true with a default even make sense here? if the client says null, then the server should probably see that and be like "ok, client says and means no value"? maybe this is purely a "UX default" again: the client exposes API to say "explicitly send a null", or "explicitly send a value", and if the user chooses neither of those, then the client sends the default value?

but maybe thinking about this with "intent" in mind isn't the right approach; maybe this is all about wire format. maybe someone doesn't like leaving fields out, and so "whatever": null really does mean (to the server) "substitute the default if you see null".

blast_hardcheese
@blast_hardcheese:matrix.org
[m]
In a tri-state world, we must assume that we're talking about presence and absence of fields, not presence or absence of values
1 reply
kelnos
@kelnos:matrix.org
[m]
but going back to your example above about the server-side stuff, i think that's interesting. are you saying in the required=true,nullable=false case, that the server should accept a request where the field is missing (or maybe eve null), but substitute the default when calling the handler? that seems a little weird to me, because to me the required/nullable knobs to me talk about the wire format, and required=true,nullable=false means that a request where that field is missing or null is an invalid request
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
Yeah, sorry -- shorthand -- I was attempting to respond to your "whatever": null example wherein you say "substitute the default if you see null"
In the case of UX where someone doesn't want to leave fields out, I could see using something akin to the Presence class to say .withFoo(Presence.Default) or something
but man that's an out-there edge case if we're optimizing for a world where hitting ".<Tab>" on a builder just brings up all the possible options
plus, the builder explicitly makes source changes on schema evolution not required, so we'd be targeting a vanishingly small userbase there on the supposition that this is even desired
there's also nothing preventing this hypothetical user from injecting an OpenAPI transformer on the plugin classpath that would strip defaults from the spec on load, or turn all required into required: true or something
I appreciate the UX consideration though, as you know that means quite a bit to me -- here, unless I'm missing something, I think too many choices could be detrimental
kelnos
@kelnos:matrix.org
[m]
agreed. just trying to consider all the possibilities, since as we know (with our original faulty handling of required/nullable) it is difficult to make changes later in a compatible way without adding an annoying command line switch, the absence of which preserves the "broken" behavior
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
Yeah. Through this conversation I've been thinking of how to model this in a graph -- the best I can come up with is an ever-branching tree, explicitly switching on each state.
Documenting this decision will be critical for users to set expectations
1 reply
kelnos
@kelnos:matrix.org
[m]
one real world data point is that someone at twilio was using the "legacy" behavior (required considered, nullable ignored), and expected "whatever": null on the server when required=false to end up giving them the default value in the handler.
kelnos
@kelnos:matrix.org
[m]

i think the thing i'm struggling with is if we should be interpreting the required, nullable, and default as instructions about the wire format, or about the UX of the server or client, or in some cases both. i think it's pretty safe to assume that required and nullable are first and foremost about the wire format, though they can affect the UX of the client and server. whereas default might be entirely about the UX of the client and server.... but we still have to make those UX decisions in the context of the wire format.

like if someone says required=false,nullable=true,default=foobar, are they saying "i just want to have a really permissible wire format, where you can leave the field out or set it to null, and no one cares, but the UX should never see a null/absent and should always have foobar substituted when there's no value or no field"?

or are they saying "absent field means they don't care and we should substitute the default, and null means they specifically want no value and we should pass that through"?

but then i try to think about the actual use cases. since CRUD is a big use case, i think... ok, on create, you probably mean the first thing. i can't think of a non-bonkers API where creating a resource has a field where you can either ask for the default, ask for null, or set a value. that just seems weird to me. but maybe someone wants to do that? then i think about update, and my feeling there is that a default doesn't ever really make sense. in that case absent has to mean "don't modify this field", null has to mean "set this field to null", and a value has to mean "set this value". and if someone does set a default value for a field in an update, i would say they are doing it wrong, because that doesn't make sense.

so maybe we can just say... ok, if you provide a default, we'll of course obey required/nullable when it comes to the wire format, but from the perspective of the server (when handling a request) or client (when inspecting a response), there is no such thing as absent or null. that property always has a value, and if we don't see one on the wire, that value is the default value. and yes, that means that if someone puts a default on their update operation, things might be broken, but that's kinda on them for doing something logically nonsensical.

outside of CRUD, are there places where someone might want that "absent=default, null=null, value=value" interpretation of what comes over the wire? maybe? can we come up with something concrete, or is this just navel-gazing? the problem is that if we decide that there's nothing concrete here, and don't allow that interpretation, and then someone comes along and says they need it, we can't support it without either breaking existing behavior (nope) or adding another command-line switch to enable it. but on the flip side, if we do add support for this case from the start, at least people who don't care about this distinction (and just want "absent=default, null=default, value=value") can still implement that in their services without too much trouble (but yes, to be fair, the UX there ends up being slightly sub-optimal).

oh damn, that was a huge wall of text.
blast_hardcheese
@blast_hardcheese:matrix.org
[m]

to make sure we're talking about the same possibilities, we've got...

  • the "wire format focused" solution, where UX is irrelevant and we emulate CRUD, take it or leave it. This covers the presence/null/value trinary.
  • the current implementation, wherein we always send the default value no matter what, because that value is baked into the POJO builders forever.
  • the server-side implementation of the previous solution, where the client can't introspect the default value smuggled along with the builder, but nulls and absent values all get decoded to the default.

Is that correct?

kelnos
@kelnos:matrix.org
[m]
i think so, yes
Stanislav Chetvertkov
@stanislav-chetvertkov
I been thinking about the following use case - when collecting usage metrics from both clients and servers in order to monitor and alert based on this information, one has to replace the actual call urls with user defined parts like Ids and user defined arguments into a grouped representation, so that, for example, /Numbers/12345 becomes /Numbers/number both to be able to group by the same endpoint and to avoid leaking sensitive data. I guess the question is whether someone has already brought up and maybe solved this issue in an automated way based inferred from the swagger specs.
Stanislav Chetvertkov
@stanislav-chetvertkov
On second though, perhaps this is easier to solve by parsing the swagger spec in runtime
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
Initially, we thought about having "extractor" style to emit a dto of the extracted parameters from any supplied route
it just never got prototyped and implemented
it would need to get hooked into the routing layer though, http4s has this functionality already, it's a little more manual in akka using the map* directives, and I don't know what it would look like for Java
the challenge comes from ensuring proper round trip for string-interpolated values, so this likely would rely on the Uri classes available for each framework
Nick Hudkins
@nickhudkins
Hi! Firstly, thank you so much for all of the work on this project. I just found it the other day and hoping to possibly begin driving adoption at work. I noticed an issue hanging out about tracing, and was curious if there was already any in-progress work. If not, I’d love to help out here, as I’m gonna need to figure it out for our use anyway.
Nick Hudkins
@nickhudkins
In my head, my approach would be to inject a parent span (not TraceBuilder) into each method of the handlers. I believe that with that, it would allow some more granular “internal” instrumentation, while continuing to allow the trace to be started (or continued in a distributed model) from request handling, and finished on the response. It’s also entirely possible that this is feasible today, and I’m not following what exists.
Nick Hudkins
@nickhudkins
Hot diggity dog I think customExtract might do the trick!
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
Yeah -- TraceBuilder is really a relic, considering the new request/response transformers. I was initially against having those, as they permit complete violation of the spec or schema, but after wrestling with TraceBuilder for so long, I was really brought around to the more pragmatic, general solution.
I'm glad you find the project helpful!
Nick Hudkins
@nickhudkins
I certainly don’t love the idea of custom extract because of the ability to violate the spec, but I think it is easier to have discipline in one place (the custom extractor) than relying on having discipline across your whole API. If I got worried about abuse, I could always create a more constrained abstraction on top to avoid it getting used “for evil” 😂
blast_hardcheese
@blast_hardcheese:matrix.org
[m]
Yeah, certainly. Plus, it gives rise to the possibility for a guardrail-contrib library dependency that fits that function signature to add a bit more structure, in a modular way
Nick Hudkins
@nickhudkins
Y’all don’t by chance use datadog APM do you? If so, I’d be happy to work together on guardrail-contrib’s first lib! (datadog apm)
Otherwise I’ll test it out here in a project and if it works I’ll extract it!