-
Notifications
You must be signed in to change notification settings - Fork 71
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
Supporting schemas for Action request bodies #84
Comments
Of course, I'm all for it. But I'm confused on one point. You talk about "inline" vs. "external". But what isn't clear to me is whether you are talking about "Inline JSON Schema" vs. "External JSON Schema" or whether you are talking about "Inline JSON Schema" vs. "External non-JSON Schema". At first, I thought it was the former (and I had actually prepared a response on that point), but in looking at your example now I suspect you actually meant the latter. Could you clarify? |
Sure. Inline means the schema is in the message. This could be an XSD as a string, but above, it's a JSON Schema object. External means the schema lives at a different location specified by a URL. This could be a link to a JSON Schema document, but above, it's a URL to an XML Schema Definition. Frankly, I just didn't want to hand-type an XSD. :) You can think of it like an HTML |
OK, so you want to support any arbitrary schema. So you expect the client to determine the format based on actually accessing the schema (i.e., looking at the supplied value)? I ask because you didn't include a field to represent the specific content type associated with the schema, e.g., |
@xogeny Ah, yes. I intended to include that, but it was a 3AM kinda thing, so it slipped by. :) A few changes below. I changed EDIT: And of course, this is a type hint. The client needs to be prepared to handle whatever the response is in a way that makes sense to the User Agent. Inline:
External:
|
OK, several questions. Are If you really want to allow both I see at least two philosophical approaches here. One could say "sure, let's make One thing that makes me a bit nervous in this who So assuming we resolve that issue, it seems to me that
The reason for doing this is that otherwise, you end up (potentially) in a situation where you say "for simplicity's sake, you can only tell the client about one schema format...uh...unless you want to specify both Those are some thoughts. I'm not trying to be difficult, I just want to point out some potential inconsistencies with the hope that we can resolve them in such a way that the resulting specification is cleaner. |
The Yes, I have been thinking of I'm hesitant to make As always, I appreciate and welcome competing points of view. I never dismiss it as "being difficult." The subject, itself, is difficult, which is why analyzing various points of view is necessary. Unifying |
Do you need to specify the media type of the schema? Could the client not
use content negotiation against the schema URL to request the schema in a
format the client understands?
…On 30 May 2017 01:06, "Kevin Swiber" ***@***.***> wrote:
The type keyword is already overloaded. It's the equivalent of an HTML
enctype in actions. For links, it's a hint on the media type that will be
returned when following the href. I think, in context, it's not too
difficult to figure out. I opt for shorter names, and that comes at some
expense.
Yes, I have been thinking of fields and schema as being mutually
exclusive. Fields are less strict and have richer semantics than any schema
definition I've seen, though arguably, JSON Schema Validation gets close on
the semantic aspect.
I'm hesitant to make schema an array and wonder if it would make sense to
have a media type that lists schema options instead. A Siren action could
point to the multi-select schema media type, and the client could choose
from there. It's worth noting that currently, Siren actions only allow one
type per action.
As always, I appreciate and welcome competing points of view. I never
dismiss it as "being difficult." The subject, itself, is difficult, which
is why analyzing various points of view is necessary.
Unifying fields and schema might have a certain tidiness, but as a
breaking change, it comes at quite a high cost. I'd be fine leaving them
mutually exclusive.
—
You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
<#84 (comment)>,
or mute the thread
<https://github.com/notifications/unsubscribe-auth/AAQ0-sWQOh_Jt7rx2Hzn1QdHD_CW4t8Rks5r-111gaJpZM4NonB9>
.
|
@mcintyre321 Good point on content negotiation. XML Schema Definitions don't have a specific media type (they use The schema |
As far as breaking changes w.r.t. |
@xogeny Can you type out an example? I thought about this, but I'm not sure how to make it un-confusing. Would it look something like this? "fields": [
{ "type": "schema", "schemaType": "application/xml", "href": "https://..." }
] Or: "fields": [
{
"type": "schema",
"value": {
"$schema": "http://json-schema.org/draft-04/schema#",
"title": "Product",
"description": "A product from Acme's catalog",
"type": "object"
}
}
] Feels a little inside-out, IMHO. |
Sure. Current (HTML fields)
Inline
External
I ditched the That is what I had in mind at least. Backward compatible, compact and covers all cases discussed so far except inline XSD. Again, just a thought. |
Just to add some thoughts: Concerning external schemas I would in my current projects most likely go with using techniques that the schema format already provide i.e. the "actions": [
{
"name": "addNewProduct",
...
"fields": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "http://schemas.host.com/product.schema.json"
}
} I do not know it there are similar ways for other schema formats, which is why I think you still need other means of linking to external schemas in the Siren spec directly. |
I would love to use an official solution to provide JsonSchemas via Siren. I kind of like the |
I would love to resurrect this topic 🙂 We have requirements that are a bit more simple than @xogeny's and would love to get them into the standard. Namely, we only need to support After all those years, I believe that those requirements are best fulfilled by using a separate My suggestion is as follows:
Examples: {
"actions": [
{
"name": "htmlStyleAction",
"fields": [
{ "name": "field1", "type": "text" },
{
"name": "field2",
"type": "checkbox",
"value": [{ "name": "value1" }, { "name": "value2" }]
}
]
},
{
"name": "inlineSchemaAction",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema-host/my.schema.json",
"type": "object",
"properties": { "field1": { "type": "string" } }
}
},
{
"name": "referencedSchemaAction",
"schema": {
"$schema": "http://json-schema.org/draft-04/schema#",
"$ref": "https://github.com/kevinswiber/siren/blob/master/siren.schema.json"
}
},
{
"name": "mixedSchemaAction",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://schema-host/my.schema.json",
"type": "object",
"properties": { "field1": { "type": "string" } }
},
"fields": [{ "name": "field1", "type": "text" }]
}
]
}
@kevinswiber Are you interested in a PR for this? In case you want to see real-world behavior first, I would love to hear your opinion first, since it will be some effort to get this implemented in my current project. It would be much easier to convince people of the implementation if you give your blessing ;) Concerning XML etcI completely understand that a true hypermedia API should take more formats than just JSON into consideration, but I suggest that for the sake of incremental improvements you only standardize JSON schema for now. By adding the I have the bad feeling that the Siren world is quite fragmented already, and by standardizing stuff that's already in use out there, everyone would benefit. You my two cents 🙂 |
@Kampfgnom Looks very nice to me. The fact that you use a previously unused keyword everywhere and didn't change any existing ones means, if I understand your proposal correctly, that it can be implemented as a pure extension to existing Siren. Note, I'm not implying it should remain that way. I think we really do need a richer way of specifying the underlying data model. As for XML, my concern previously was trying to avoid the current issue of effectively only supporting HTML forms. Expanding it as you have outlined means that it then becomes HTML forms + JSON and that further extensions would be required for actions that POST, for example, XML data. Personally, I never use XML and I build all the APIs I use myself. So I'm fine with what you are proposing. I'm just noting that it would require further "extensions" for users who wanted to support other formats (e.g., XML, protobuf, etc). |
@xogeny I understand/share your concern about multiple schemas. On the other hand I requested @Kampfgnom I like your layout, especially that inline and referenced schemas are possible (which is great for client side caching). This is what I need to feed schemas directly to form generators. Another solution would be to tell the server about the desired schema format, i did tell him I want Siren, so why don't tell him I want json schemas :) |
@Kampfgnom IMHO 'standards' should come from (multiple) real world implementations and seeing what works and what doesn't, not from being 'blessed first' then implemented. That said, if your use case has a need for a non-standard extension you should do it to fit your needs (and document the pros/cons for the community). There are no standards police going to show up and yank your ability to use Siren ;) |
Speaking of implementations, perhaps it is worth sharing something I've been working on because it may provide a means for exploring this topic but without necessarily changing Siren. Obviously, I've been on the "richer schema for actions" band wagon for a while. To me, schemas provide an important means for validating data as well as form generation. So I'm all for it. But one downside worth mentioning in this discussions is the degree of redundant information potentially presented by the Siren server. While it is true that a schema can be represented as a reference (thus minimizing the number of characters required to convey the structure of an action payload/URL), it really is static information that need not be bundled with a response. Note that it isn't just the fields. Basically, all the fields associated with an action are typically static and the only "dynamic" aspect (the thing that might change from payload to payload) is whether a given action is "available" (whether that affordance is permitted by the current client). But once you start thinking along these lines, there are a couple of other interesting things to note. For example, there is no way to convey what to expect in the properties of a resource. Similarly, there is no way to convey what relations you might find associated with a given resource and what resources those are likely to point to (and how many there might be). Again, much of this is static. In fact, many approaches like OpenAPI/Swagger have up front specifications for this "static" information. But those formats do not necessarily lend themselves well to hypermedia APIs and tend to be more "CRUD" oriented with fixed resource URL patterns, etc. After RestFest in Grand Rapids, I started playing around with the idea of writing up specifications for hypermedia (but in my case pretty Siren-centric) specifications for APIs. The idea is that a siren response could come with a simple At the moment, I've got a prototype that can generating TypeScript type definitions for properties of resources and action payloads automatically from the specification. The definitions can be shared by both the client and server to ensure that the payloads being exchanged over the wire conform to a common "shape". I'm also able to generate documentation that automatically generates a graph (both per resource and for the entire API) that shows the resources types, resource properties, actions, what relations exist between resources and the cardinality of those relations. The next thing I'd like to add is to generate (per API) a client navigation library specific to that API. This library would be generated from the specification, include static types for all navigation (checking payloads of all requests and types of responses) and it would allow navigation using the resources and relations named in the API, e.g., let orders = await api.findCustomer({ name: "ACME" }).follow("orders");
orders.sort((a, b) => a.orderData > b.orderDate); This would all be Siren payloads behind the scene and, again, typings would ensure that everything here conforms to the expected data being passed into and back from the API. I already have a library called Part of the plan here is that during development, validation checks would be put in place during testing to ensure that payloads being passed around conformed to the schemas specified. Furthermore, the profile already allows resources, relations and actions to be marked as deprecated allowing further checks if you want to have a strictly conforming interaction with the API (i.e., no deprecated calls). Why am I mentioning all this in this thread? Well in my use case, all this schema stuff is kept in the profile and the action responses are minimal (the information simply isn't needed if the client was constructed from the profile...it already knows all this). The bottom line here is that this project of mine is currently in a private repository. But if people are interested in this approach, let me know and I can make it public and we can perhaps move this discussion over there and all get out of @apsoto's hair. 😄 |
Swagger is in deed CRUDy. But I recently learned that links are introduced in v 3.0 which is good. Graphs and linked types/classes, delivered by the API seem a interesting approach. There is also a danger in becoming the new WSDL though :) I did also work on a prototype for a type save client, which would fail early. I (hand wrote) some client side classes for responses and pass it to a generic client. It looks something like this: var hypermediaObjectRegister = CreateHypermediaObjectRegister();
var sirenClient = new SirenHttpHypermediaClient<EntryPointHco>(ApiEntryPoint, hypermediaObjectRegister);
var customersAll = await sirenClient
.EnterAsync()
.NavigateAsync(l => l.Customers)
.NavigateAsync(l => l.All);
var customer = customersAll.Customers.First();
var actionResult = await customer
.CustomerMove
.ExecuteAsync(new NewAddress {Address = "New Address"});
Assert.IsTrue(actionResult.Success); Would realy like to continue discussing about discoverable documentation/schemas and clients. |
Just to add to the "existing implementations" part: Our current way of working involves hand writing a small wrapper client around a more general siren client, that only encodes relation- and action-names. The resulting code looks like this: const buildings = await client
.entryPoint(context)
.generalManagement()
.findBuildingsOfCompany({ customerNumber: '123' })
.items(); That being said, I acknowledge everything said about specifications and generation of clients. But I would like to come back to the "incremental improvements"-part 😉 At the same time I acknowledge @apsoto's comment about "multiple real-world implementations". All in all I'm happy to see that our current approach is at least "not bad"™️ . I will now incorporate it into a part of our solution and we'll see how that goes. Since I am working for a big'ish customer, I hope that we all can get some meaningful result from that 🙂 |
@Kampfgnom and @MathiasReichardt It isn't clear to me from your responses whether you are interested in looking at this profile specification stuff. If so, I'll write something up about it and publish it. Just let me know. My email is associated with my GitHub ID, so just reach out to me there so we don't take up any more space in this thread about that. |
@xogeny I have some questions. I have to say that I find very interesting what you are describing and the general discussion here, but I want to make sure that I have understood completely what you describe.
I would say that metadata (hypermedia etc) is relatively much less volatile than the data. By having them through a reference link, you can add whatever caching headers you want through HTTP, so it doesn't always need to be bundled with a response. Most of the times, you just need to fetch it once.
If I understand you correctly, we don't have such spec as of now for associations etc, that doesn't mean that it's not possible through those "rich schemas". Personally I would love to have such specs ready but it's not easy and in my opinion ideally we should opt for compsability, i.e. compose different specs together instead of creating a complex monolith that tries to solve everything at once. But that's another discussion..
I never liked Swagger/OpenAPI for that reason: their thinking is to create the specs offline and from that go and scaffold code and possibly documentation. If Swagger allowed me to expose the API specifications online in a standard way, without any change I think it would be fine (maybe they can do that now? I haven't touched it for a while). If that was possible, then we could talk about patterns, efficiency and good design for exposing those metadata, compare and contrast with your (or anyone else's) solution.
Curious: how is the Also, from what I understand, the difference in your implementation compared to conventional hypermedia-based API-agnostic clients is that you first fetch the metadata of the API (hypermedia, fields, types, associations etc) and then you do the API requests (after you build your client). Am I right ? If that's the case, given that the API design allows you to do such thing, I am pretty sure that's the best practice actually and not an edge case. Again if I have understood you right, I don't feel that this is much different from Swagger, only that is done the right way. As I said before, unfortunately Swagger focused on scaffolding stuff rather than the self-discovery of API metadata/hypermedia through API introspection.
I am wondering how you have structured all those metadata (I feel metadata is the right word here and not hypermedia as hypermedia is a subset of metadata and mostly refers to links and actions. Correct me if I am wrong). Would you like to share more information ? If I can do a high level overview: what you are describing isn't a meta-API for your API that you exploit to setup your client beforehand ? How is that different/similar to a generic API introspection (regardless the specs/profiles used)? @kevinswiber I like the initiative here, but I would like to be able to negotiate for the schemas that I understand. And I don't mean XML vs JSON Schema, but also negotiate for which version of JSON Schema. However, I don't think that's Siren's responsibility and should left to HTTP. But I feel that this is gonna be super complex with proactive-negotiation (I have tried such discoveries with similar things but it's a chicken-n-egg problem in proactive negotiation..) and reactive negotiation would help a lot. So I guess I don't help you much here but if you could find a way to support that it would be awesome :) |
OK, I think we've gone too far beyond the scope of this issue. I've inadvertently hijacked this whole thing. To put an end to that, I've made my current work in progress public here: https://github.com/xogeny/hyprofile I ask that if @vasilakisfil, @MathiasReichardt and @Kampfgnom (and anybody else) have comments or questions, they log them as issues in that repo. Thanks! |
Is there any progress on this idea? |
I just wanted to drop a note in here about this topic 4+ years on. I contributed to this thread previously as @xogeny but as that company has since been acquired, I'm just commenting under my personal Github account. I wanted to try and stay as conformant to Siren as I could. I ended up handling this in effectively two ways. The first was to establish a
The nice thing about what I've described so far is that it doesn't require changing the Siren specification. Furthermore, I see the But I also added a
Now, I don't actually expect to use the export interface Action {
name: string;
class?: string[];
method?: "GET" | "PUT" | "POST" | "DELETE" | "PATCH" | "HEAD" | "OPTIONS";
href: string;
title?: string;
type?: string;
fields?: Field[];
} Note the absence of a So, in practice what I will end up doing is that for APIs that support the special Now, all that being said, I do think it would be nice to have a standard field in the action itself. It just seems like a nice way to extend the self describing nature of Siren a bit more. Frankly, there are rarely applications where I can use the HTML-ish field descriptions that are standard in Siren and even in those cases where I do, using libraries like
Anyway, I just wanted to share this with others. It doesn't really negate what I think is still an open need for a |
See some discussion on #65.
Hi, folks. I know a solution to this topic is long-awaited.
Copying an example from the above thread (specifically, @xogeny's), here's a proposal for actions that have a request body with a typed schema of some sort.
I think I'd like to see Siren be explicit about whether the schema is inline or external.
Inline:
External:
Of course, we need language stating that if a schema exists,
fields
should not be present. And probably more, but we can work that out. Happy to listen to any feedback.The text was updated successfully, but these errors were encountered: