-
Notifications
You must be signed in to change notification settings - Fork 114
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
Implement fmt.Formatter #72
Conversation
Codecov Report
@@ Coverage Diff @@
## master #72 +/- ##
==========================================
+ Coverage 98.92% 99.05% +0.13%
==========================================
Files 4 4
Lines 279 318 +39
==========================================
+ Hits 276 315 +39
Misses 2 2
Partials 1 1
Continue to review full report at Codecov.
|
uuid.go
Outdated
case 'q': | ||
_, _ = io.WriteString(f, `"`+u.String()+`"`) | ||
default: | ||
_, _ = io.WriteString(f, u.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'm not convinced about this approach.
When UUID
does not implement fmt.Formatter
, this program fails the go vet
check, and produces obviously wrong output: https://play.golang.org/p/stts25e3_2a
However, when UUID
does implement fmt.Formatter
, the program passes go vet
, and produces more plausible looking (but wrong) output: https://play.golang.org/p/hNOR123ZjkU
The behavior in the second case seems dangerous to me. Using the wrong formatting verb is a programmer error and this implementation of fmt.Formatter
hides it from go vet
, and swallows it when it produces output. I don't know what a good solution to this problem might be, short of trying to re-create the original format string from the fmt.State
and calling fmt.Sprintf
with a [16]byte
value instead of a UUID
, to avoid recursion.
For reference, pkg/errors
implements fmt.Formatter
on some of its types, and when it doesn't understand a verb, it simply produces no output, e.g. https://github.com/pkg/errors/blob/master/stack.go#L64-L84. I'm not a huge fan of this approach either, but it is at least a data point we have, if we're going to implement fmt.Formatter
at all.
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.
There doesn't seem to be any way to indicate that a formatting verb is invalid/not supported, so it feels like "do nothing" is the only option. I'm okay with that path for all of the formatting verbs that don't apply: %b, %c, %d, %o, %U, %e, %E, %f, %F, %g, %G.
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 clarify, "do nothing" means "emit no output"?
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.
Correct, "do nothing" would be to return an empty string for all of those verbs.
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.
Okay. I guess that's reasonable, and there is a precedent for it too. But my concern with respect to the fact that this conceals programmer errors such as using the wrong flags remains.
The fmt.Formatter
implementation is opaque to static analysis tools like go vet
. We're gaining convenience and a nice API for printing UUIDs as hexadecimal strings, but we are losing precision under the lens of static analysis tools. We should decide if this trade-off is worth making.
To clarify: I'm not sure we should be doing this, but if we decide to, then I think it's not ready yet, and we need some good answers, especially to the third comment I left above. |
What's wrong with the caller using strings.Replace()? https://play.golang.org/p/fPJoSEQjZ1t |
@cratermoon Technically, nothing. Your example generates the desired result. But it loses context (because returning the hex digits of a UUID is not what @acln0 I'll go through your suggestions tomorrow. Thanks for the thorough feedback. |
As an alternative to implementing |
hex.EncodeToString(u.Bytes()) |
For reference, I'm personally not a fan of the "a little duplication is better than ..." mantra. To me, this is a logical operation on the |
I updated |
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.
Thank you for opening this PR, I think it's good feature for us to consider.
As part of #71 we're working towards removing an implementation within this package that falls outside of the RFC that standardized UUIDs, bringing us fully in alignment with the standard. This change would introduce functionality that makes us support non-standard compliant functionality[1].
I'm on the fence about supporting non-standard functionality, when it doesn't seem like a common need. Speaking from personal experience, I don't believe this format is widely used because it's against the RFC; and if that's not true please let me know.
Now looking at the RFC link above, it does show that a capitalized hexadecimal format is also supported and so I think the concept behind this change still solves a need. We could add the ability for consumers to render their UUIDs with capital letters instead of only lowercase letters.
For this PR as it is today, the question I think we need to answer is whether we want to take on the cost of maintaining code for those who aren't standards compliant? Secondarily, do we also want to support capitalized hexadecimal UUID formats? @gofrs/uuid
The current implementation of I don't know that we should limit the functionality to only what's defined in the RFC. Personally, I lend more weight to whether or not the functionality is logical and useful without breaking compliance with the underlying RFC specification. I think this PR adheres to all three of those conditions. As far as usefulness, I've had a need for encoding/decoding UUID values in this format on two separate occasions now. I submitted the PR based on a guess that others may need/want this same behavior also, without punting to "You can do it yourself if you just ....". I have no issue with the library implementing a superset of the RFC but I am far from the only one with an investment and/or opinion here. |
This looks good. Wondering if we can add two other features to this to make it 💯. I'm thinking I could hold the PR removing |
I'll try to make these changes some time today |
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.
LGTM!
…urque/uuid into implement-fmt-formatter
Implement fmt.Formatter Signed-off-by: Tim Heckman <t@heckman.io>
@theckman any plans to cut a new release with this change in? |
@martin308 3.3.0 will be out within the next 30 minutes. |
@martin308 https://github.com/gofrs/uuid/releases/tag/v3.3.0 Sorry for the delay, a few of us could've sworn we released it. Thanks for the ping! |
Amazing, thank you! 🙏 |
I had a use case where I needed to get the string representation of a UUID with only the hex digits. Implementing
fmt.Formatter
seems to be the prescribed way to provide custom string formatting in Go, so I did so. Additionally,UUID.String()
returns the RFC-compliant string so I did not touch that code.The standard
%s
,%q
and%v
format runes have the same behavior as before. I added%h
and%H
for outputting only the hex digits without the dash (-) separators.%h
uses 'a'-'f' and%H
uses 'A-F'