-
Notifications
You must be signed in to change notification settings - Fork 26
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
Handle unknown tags via PassthroughConstructor. #38
Conversation
This probably addresses #23 |
Why is
Sure, maybe a different PR? |
Let's assume MIT (https://github.com/owainlewis/yaml/blob/master/LICENSE) until otherwise proven. |
I can think of three reasons:
I could very well be misunderstanding your question, though. Is there an alternative you'd prefer? |
@grzm I'm not deeply familiar with this library, but since you're porting from owainlewis's repo, I checked there and he seems to pass the constructor like this: |
Oh, now I see it: Well, this PR looks good to me then. What about you @lread ? |
Hiya! 👋 Ramping up on this one, so please bear with me! Here's the original commit message from owainlewis/yaml:
So:
I don't see docs around The snakeyaml docs mention:
Ok. So we use the safe constructor by default. And the mark constructor comes from clj-yaml and is explained by its tests. As a general user, I'd probably just like to just choose features. As an advanced user, I might want to provide my own snakeyaml constructor. These are not strong opinions, just my thoughts and notes while learning about all this. Also: have we thunk (pun intended) through our naming? If we go this route, I wonder if |
Another thought: exposing a snakeyaml constructor would tie us to snakeyaml and make a change like moving to snakeyaml-engine a potentially breaking change for users for clj-yaml. |
@lread True, but at that point we could just release an all new clj-yaml 2.0 (with an effort to keep much of the API the same) |
I wonder, does clj-yaml currently expose snakeyaml implementation details anywhere else yet? More questions:
If nobody is in any great hurry, I am interested in two experiments (I'd volunteer to do the work).
I don't mean to bog this PR down with so many questions. |
One such thing is exceptions. I needed to expose this one in bb for people to properly handle YAML exception. I don't know if snake-yaml-engine has exactly the same exception type. |
Ah thanks, @borkdude, good to know, clj-yaml leaks snakeyaml exceptions. No, it does not seem that snakeyaml-engine shares the same exception classes. Snakeyaml: Snakeyaml-engine: So being new to this project, I should probably become aware of what clj-yaml wants to be:
Not suggesting one is better than the other but important to understand. |
Regardless of what it wants to be, I think it would almost certainly breaking if we silently moved to some other implementation: the risk is too high. We could at some point release a breaking change version 2.0.0 or so which does that and is almost a drop-in replacement so people could move over without much ado. |
I'm starting to lean towards the idea that we should move forward with cli-commons/yayama (think Yo-Yo Ma + yayaml), but name TBD) on snakeyaml-engine. It looks like it has a smaller surface area (json types only). I think we could probably lean into the choice of underlying library: I think there's a lot of abstraction work that needs to be done to truly make the underlying implementation changeable. Then again, what's the value if it's just a convenience wrapper? I've generally been displeased with wrappers, especially when the ergonomics of the underlying java lib aren't that bad: the layer of indirection gets in the way. |
Because I can't leave well-enough alone. I hate the names and I'm not happy with the keyword args, and neither round trip, but the UnknownTagConstructor does this: Parameters:
paramEnvironmentType: # ask a user to define whether it is 'dev' or 'prod'
Description: Environment type
Default: dev # by default it is 'dev' environment
Type: String
AllowedValues: [dev, prod]
ConstraintDescription: Must specify 'dev' or 'prod'
Conditions:
isProd: !Equals [!Ref paramEnvironmentType, prod] # if 'prod' then TRUE, otherwise FALSE
Resources:
myVolume: # create a new EBS volume only if environment is 'prod'
Type: 'AWS::EC2::Volume'
Condition: isProd # conditionally create EBS volume (only if environment is 'prod')
Properties:
Size: 100
# etc. with {:Parameters
{:paramEnvironmentType
{:Description "Environment type",
:Default "dev",
:Type "String",
:AllowedValues ("dev" "prod"),
:ConstraintDescription "Must specify 'dev' or 'prod'"}},
:Conditions
{:isProd
#:clj-yaml.core{:tag "!Equals",
:value
(#:clj-yaml.core{:tag "!Ref",
:value "paramEnvironmentType"}
"prod")}},
:Resources
{:myVolume
{:Type "AWS::EC2::Volume",
:Condition "isProd",
:Properties {:Size 100}}}} with {:Parameters
{:paramEnvironmentType
{:Description "Environment type",
:Default "dev",
:Type "String",
:AllowedValues ("dev" "prod"),
:ConstraintDescription "Must specify 'dev' or 'prod'"}},
:Conditions {:isProd ("paramEnvironmentType" "prod")},
:Resources
{:myVolume
{:Type "AWS::EC2::Volume",
:Condition "isProd",
:Properties {:Size 100}}}} (That reminds me of another thing I'd like to have in version 2: YAML sequences as Clojure vectors, not sequences/lists) |
(The signing GPG failure isn't me, right?) |
The GPG failure is @slipset's - perhaps he can come up with something that doesn't make PRs seem like they fail? ;) |
@grzm I like these solutions. Perhaps the name About sequences: these are sequences for a reason, because YAML allows recursive / infinite / circular references. There was an issue in the past to coerce to vector. I would want to have this as an option since the 99% case is that you won't have these circular references. Perhaps we could have |
@grzm I just merged a ci change to master that should fix these GPG deploy failures we are getting on PRs. |
Okay, I'm adequately happy with the new names. However, the names have an interesting contrast with the passthrough-constructor from owainlewis/yaml:
The reason I've landed on these names is that these are for handing unknown tags, and I wanted names that reflect this. There may be a bit of confusion for those moving from I've removed the generic constructor option to avoid exposing more snakeyaml implementation details. I did have a thought of passing a function that handles a node with an unknown tag, but I think that ends up exposing some of the snakeyaml internals anyway. This should handle my personal use case (I'll be using the One minor thing is whether the namespaced should just be If this looks acceptable, I'll add some notes to the README and submit a single-commit PR. |
@grzm Feedback: DesignI think having just one option which is a function which gets a map with :default-tag-fn (fn [m] (:value m)) or simply: :default-tag-fn :value would be the same as StyleI'd like to avoid using question marks for boolean keyword options. Question marks are usually used for predicate functions, not boolean values. E.g. Clojure metadata uses |
One more try: minor difference from suggestion of passing a map with One motivation for this is that I was hoping to pass |
I don't really like the idea of using a positional function: this is really inflexible when it comes to future changes. |
Yes, you would have to provide some function to a sensible transformation, but the 99% use case will probably be |
I beginning to feel that the bike shed really should be yellow. ;) This particular handler is specific to its use case. It's not a generic transform function that will be used in any other pipeline. I did consider following the equivalent for functions that take MapEntries, and use a single argument consisting of a two-element tuple compare: (map (fn [[k v]] ,,,) {:foo :bar, :baz :bat}) |
(Also for cli-yaml v2: follow clojure.data.json and cheshire for handing keywords; default to not keywordizing, and take :keyword-fn instead of a boolean) |
@grzm The reason I'm also suggesting
but the usage of positional functions has a serious drawback of breaking future callers when you add things, that is why I suggested a map. It's pretty standard API design practice nowadays. |
Yes. Maps everywhere is common pattern. Another common pattern is (defn unknown-tag-fn
([tag value] (unknown-tag-fn tag value {})
([tag value opts] ,,,)) This also allows extension, while providing ease for the base case. I use both, which one depending on what is more ergonomic in a given context. To me, it's not one-size-fits-all. Re: default-tag-fn: I didn't realized the parallels with |
This is why I added:
|
Having a multi-arg fn option isn't a good API imo, since you cannot detect (without hacks) if a user provided a 2-arg or 3-arg function or both. Yes, you could try calling the function in a try/catch with 2 or 3 arguments first, but why bother if you can implement it robustly from the start using a 1-arg map-receiving function. |
To summarize: I think I agree 95% with your design, it's just that:
|
Thanks for processing the feedback!
I think we reached this point now. Single commit isn't necessary, we can squash-merge this, but if you prefer, single commit is ok. |
I updated the README in the last commit. Feel free to squash merge. Thanks! |
@grzm Will do, but there are conflicts now. Is it possible that you fix these? Could also do this locally, if you want. |
@grzm Already on it. Merged version pushed here now: https://github.com/clj-commons/clj-yaml/tree/grzm-passthrough-constructor Will merge from there. |
Cheers! I was working on fixing the conflicts but haven't been able to steal enough time between sessions at the conference. Thanks everyone for their feedback! |
I figured as much. Enjoy the conf! |
Here's a port of the PassthroughConstructor from https://github.com/owainlewis/yaml
A couple of notes:
:constructor
keyword argument to make it easier for people to use their own custom constructors, not just the passthrough-constructor.if
s to determine the constructor into acond
.:unsafe
,:mark
and/or:constructor
keyword args. There's currently no other logging in the lib, though, so maybe it's not worth it.parse-string
in the README. I'll do that if it's thought to be useful as well.