-
-
Notifications
You must be signed in to change notification settings - Fork 20
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
Share configuration between collections #290
Comments
@LucasPickering Is this a feature you'd still like to get added? I would love to look in to implementing this next (after I wrap up the ChainSource::Select stuff!). When I initially started using slumber for testing my work's APIs this was one of the first "ah, bummer" things I hit (we use a micro-service architecture with shared auth - as you noted would be one of the biggest advantages of such a feature). |
@anussel5559 Yes definitely, but this is a very significant feature. The first step is figuring out a design for this. Do you have any thoughts on how you would want it to work? |
yea! happy to brainstorm here. Some of the complication/intricacies I see are:
I think the easiest approach for users to grok might be one where they can build a One approach for dealing with the id concerns is making the shape of the This allows the user to intuit how to reference a resource across collections. We can additionally panic/quit parsing if we detect any collection with a duplicate id as part of the Collection hierarchy. This might look like this: --- base.yaml
profiles:
works:
name: This Works
data:
host: https://httpbin.org
username: xX{{chains.username}}Xx
user_guid: abc123
chains:
username:
source: !command
command: [whoami]
trim: both
--- service1.yaml
extends:
- file: 'base.yaml' # relative filepath
id: base
profiles:
works:
name: This Works
data:
host: https://httpbin.org
username: xX{{base.chains.username}}Xx
user_guid: abc123
chains:
password:
source: !prompt
message: Password
sensitive: true
requests:
login: !request
method: POST
url: "{{host}}/anything/login"
authentication: !basic
username: "{{username}}"
password: "{{chains.password}}" The link is explicit in the target collection, and the collection can extend multiple additional collection. There's still some hand-waving to think through here, but hopefully this is a thread to start pulling on? |
@anussel5559 This is a good start. My first thought is that you use the words "extends" and "inheritance" but this feels more like "import" and "composition" to me. You're declaring an external resource (the "extended" collection) and assigning it an alias, then reference that alias selectively to import data from the external resource. To be clear, I prefer this approach over "true" inheritance (where everything from the parent is implicitly brought in), just want to be clear on terminology. I used an inheritance structure for a previous project env-select, and it worked alright there but it opens up a rats nest of both behavior and implementation issues (e.g. how deep do you go when merging data? do arrays concat or overwrite? etc). With your design, I'm curious how you would reference things other than chains from the imported file. E.g. if you wanted This feels functionally similar to the option I've been leaning toward of using URIs to reference objects between . I would take a look at the openapiv3 crate. The OpenAPI spec makes heavy use of this pattern, allowing you to easily reference complex objects within a file or between files. (Note: In my original post I called this JSONRef but after doing more research I'm not sure if that's exactly what OpenAPI uses. It looks like JSONRef is an extension of JSONSchema, and Slumber collections certainly aren't JSONSchema compliant). The key to this in pub struct Recipe {
#[serde(skip)] // This will be auto-populated from the map key
pub id: RecipeId,
pub name: Option<String>,
pub method: Method,
pub url: ReferenceOr<Template>,
pub body: Option<ReferenceOr<RecipeBody>>,
pub authentication: Option<ReferenceOr<Authentication>>,
pub query: ReferenceOr<Vec<(String, Template)>>,
pub headers: ReferenceOr<IndexMap<String, Template>>,
} There are certainly some design decisions to be made around what should/shouldn't be referenceable, but that's the general idea. ExampleThis is an extension of the example in the original post, to show some more use cases: #common.yml
chains:
username:
source: !command
command: [whoami]
trim: both
password:
source: !prompt
message: Password
sensitive: true
auth_token:
source: !request
recipe: login
trigger: !expire 12h
selector: $.form
requests:
login: !request
method: POST
url: "{{host}}/anything/login"
headers:
Accept: application/json
body: !form_urlencoded
username: "{{username}}"
password: "{{chains.password}}"
get_current_user: !request
method: POST
url: "{{host}}/users/current"
headers: !reference #/recipes/login/headers
authentication: !bearer "{{chains.auth_token}}" #service.yml
chains:
auth_token: !reference common.yml#/chains/auth_token
requests:
get_current_user: !reference common.yaml#/recipes/get_current_user
get_users: !request
name: Get Users
method: GET
url: "{{host}}/get"
authentication: !bearer "{{chains.auth_token}}" Pros
Cons
ImplementationThe implementation for this I think would look something like:
We would also need to figure out how to make this as transparent as possible in the rest of the app. After loading and resolving, references should be transparent to any code that uses the collection. That's a lot of words. Thoughts? |
Thank you for adding some precision to my language there! I tend to be a little too flippant with my word choice, so my apologies. Ah - I DO like this referential approach, but I worry a bit that it might feel awkward to wield. There's a bit of repetition between the two files - in your example the base collection defined the In my head, this That's where I'll leave my thoughts on the |
@anussel5559 To me, explicitness is probably the most important consideration for this system. I've spent a lot of time at my job looking at Gitlab CI configuration files, which support an inheritance-ish structure where you can arbitrarily import resources from other files/repos. It means reading CI config turns into a game of repo browsing and ctrl-f-ing. I really want to avoid that. I think the repetition of the chain definition here is a good thing, because of the explicitness. I look at it as akin to a Another thing I just thought of: Right now, Slumber watches the collection file and reloads automatically when it changes. As part of this scheme, we should consider collecting a list of upstream collection files as well, and watching all of them so that upstream changes automatically reload as well. It might also be worthwhile to include a diffing step to make sure we only reload when things that we actually care about were modified. |
Awesome - I think that all makes a ton of sense. I'm still happy to take a shot at building this out if that makes sense to you! |
@anussel5559 Yeah go for it! Similar to the the select list, try to break this into a few MRs. Let me know if you want any more input on it. |
@LucasPickering I wanted to get some thoughts down on the approach here. One thing that's been bugging me is when to do the reference resolution. I'm starting to feel pretty strongly that during app startup, AFTER deserialization, there should be a step of reference resolution. This way we can collect all the unique files from the reference URIs spread throughout the collection, parse the unique files once and then assign in the appropriate struct based on the URIs fragment. If we place reference resolution inside of deserialization, we run the risk of slowdown, ultimately parsing potentially the same file any number of times. If that makes sense, I think I'd do something along the lines of:
How does that land / jive with your thoughts on the outcomes here? |
@anussel5559 Yeah, I think that's the right way to do it, with an intermediate step between deserialization and usage. I really don't like the idea of maintaining two versions of the entire collection tree, so I'm thinking it might be best to do all of this in a separate crate that can be generalized. It could use a derive macro to make it easy to mark which fields are resolvable. An example:
and the expanded code would look something like:
This definitely adds complexity to the implementation but I think encapsulating all that in a separate library will make it much more maintainable. Are you interested in doing that? If not I'm happy to do the library. Also happy to collaborate on it. |
yea, that's exactly how I was thinking of it! I'm still happy to build that library, though I'll probably move somewhat slow. I found a VERY similar crate (OptionalStruct) that nearly does what we want though it is strict in that it only supports wrapping with the |
Alright - I've got a fully working implementation of this macro here! I still have some documentation, test cases, and publishing to work out. The Effectively, we'll have to implement the
Then, all that has to happen is:
Lemme know what you think so far! |
@anussel5559 I'm on vacation so I haven't had time to look through all the code of |
no rush! I'm not sure I follow though - the macro is designed to take in any enum, so it doesn't seem like it could ever know about how to resolve all the variants of its wrapping enum to the underlying struct type without being told what to do. |
@anussel5559 I'm actually not even sure if what I'm suggesting is possible. I'm going to play around with it a little bit and see if I can figure out something that works |
Update on this: I've spent the last month or so poking around with a variety of solutions. I have something very primitive that's working, but I'm questioning whether this is the right solution, or potentially if I should replace YAML altogether with a different solution that can handle references (as well as other qol improvements) natively. So I haven't forgotten about this, it's just slow going. |
Nice! I'm still happy to help here however I can, just let me know! |
Did you search for existing issues already?
Yes :)
Is your feature request related to a problem? Please describe.
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
It should be possible to import elements of a collection from one file to another. For example, if you use the same authentication process for several microservices, and each service has its own
slumber.yml
file, it would be nice to reuse the chain/request that provides authentication across all collections, so you only have to write it once.Describe the solution you'd like
A clear and concise description of what you want to happen
A potential solution would be to support JSONRef is some parts of the collection. For example:
Describe alternatives you've considered
A clear and concise description of any alternative solutions or features you've considered
extends: common.yml
and transparently use everything from itAdditional context
Add any other context or screenshots about the feature request here
The text was updated successfully, but these errors were encountered: