Skip to content
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

feat(repository-json-schema): add in top-level metadata for json schema #907

Merged
merged 2 commits into from
Jan 30, 2018

Conversation

shimks
Copy link
Contributor

@shimks shimks commented Jan 24, 2018

  • added decorators to provide top-level metadata intended to be used for JSON Schema generation
  • can now take in:
    • title (model)
    • description (model)
    • required (property)
    • oneOf (property)
  • point of interest: Definitions of PropertyType and ModelDefinitionSyntax have changed. Please take a look at how their new definitions would affect the juggler implementation
  • connected to [Schema] TypeScript to JSON Schema #786

Checklist

  • npm test passes on your machine
  • New tests added or existing tests modified to cover all changes
  • Code conforms with the style guide
  • Related API Documentation was updated
  • Affected artifact templates in packages/cli were updated
  • Affected example projects in packages/example-* were updated

@shimks shimks added this to the Sprint 54 milestone Jan 24, 2018
@raymondfeng
Copy link
Contributor

@shimks I have two questions:

  1. Should we align the decorator names more with TypeScript or JSON schema terms? For example, oneOf vs. unionType?

  2. Do we prefer to have many fine-grained decorators or only a few taking metadata as an object?

bajtos
bajtos previously requested changes Jan 26, 2018
}
};

if (meta.title) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The title property does not make sense to me. IMO, the title should be always set to the model name.

See e.g. http://json-schema.org/example1.html#starting-the-schema

{
    "$schema": "http://json-schema.org/draft-06/schema#",
    "title": "Product",
    "description": "A product from Acme's catalog",
    "type": "object"
}

I am imagining such schema would be created from the following code:

@model({
  description: 'A product from Acme\'s catalog',
})
class Product {
}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the harm in allowing it to be overridden, though I'd expect that to take the form of result.title = meta.title || ctor.prototype.name; or something similar

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough. Even though it's not required for MVP, it looks like a small addition with big benefits.

@shimks Please make sure there is a unit-test covering all different combinations (title not provided - model name used, title provided - model name is ignored) and also the documentation mentions this behavior.

if (propMeta.validationKey === 'oneOf') {
let property: JsonDefinition = (schema.properties[prop] =
schema.properties[prop] || {});
if (!property.oneOf) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AFAIK, juggler does not support oneOf constraints. Juggler allows clients to store values violating this constraint and as a result, the REST API will return responses that are not conforming to the OpenAPI spec.

I am proposing to leave oneOf out of this pull request. First of all, it's out of MVP scope. Secondly, we should implement support for oneOf in juggler first, and only then expose it via decorators.

Please correct me if my understanding is wrong and juggler does support oneOf constraint.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interestingly enough ,the acceptance criteria for #786 say

For @model:
Add top-level specific metadata to the entry for this type:
(...)
oneOf (it's a union type!)

I find this confusing - oneOf is related to @property, not @model decorator, AFAIK juggler does not support oneOf and I don't understand why is this required for our MVP scope? AFAICT, https://github.com/strongloop/loopback-next/tree/master/packages/example-getting-started does not use oneOf at all.

@kjdelisle could you please clarify?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm relatively sure that oneOf is not supported juggler as well. Since support of oneOf was an MVP goal I've put in support for it and coded under the assumption that not every properties that get put into @model decorator had to be juggler related.

I think this just goes to show that we need to extract out the decorators into @loopback/model package of some sort to just have a general container for information that juggler and json-schema can use. This will require a refactor and I'm not sure when it should be done, but I think it'll save us a lot of headaches in the future.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bajtos

The metadata we're producing in JSON Schema form is not the literal metadata that will be consumed by the Juggler, IMO. The primary purpose of making this JSON Schema is that it's the most descriptive form of metadata available to us after compilation, and consumers can freely use whichever parts of it they'd like and ignore others. The legacy juggler bridge does not need to support oneOf, but I think it would be worth it if the new Juggler can handle these types, since it would more neatly allow for integration with the newer OpenAPI spec.

We could just create the metadata we want for the current Juggler DSL directly, but I think that would be a mistake because it would limit our understanding of what could be compatible in the future. Using JSON Schema as the common ground comes at the cost of a slightly slower startup time, but gives us compile-time and run-time ways of seeing what we can transform representations into. (EDIT: to clarify the slower startup time, I meant that we would generate the JSON schema and the Juggler schema afterwards)

This means that extension developers can:

  • Work against an existing standard for developing their own representations for plugins, rather than conforming to our homegrown format
  • Use those new formats with the Juggler by converting them, and knowing they can do so by testing that their metadata readily converts to and from JSON Schema

Ideally, the new Juggler DSL would be a strict superset of JSON Schema, and would happily implement all of its types and representations.
cc @raymondfeng

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@kjdelisle I see your point about using JSON Schema as the common ground. I have raised this point in the past, but @raymondfeng did not agree IIRC. If we decide to pursue this direction, then we must make it easy to use and tell the user when they are doing something that does not work (like using oneOf on a model that's persisted by juggler that does not support oneOf validation).

I also don't see why should oneOf be part of our MVP? Remember, we defined the MVP scope as whatever is needed to allow LoopBack4 developers to easily build our getting-started application. AFAICT, this application does not need oneOf, therefore oneOf is not part of our MVP.

I consider this as a scope creep, I really wish we could do better in keeping focus on what's absolutely required by MVP. I'm sure it will help us to deliver more of what we committed to in our monthly milestones.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also don't see why should oneOf be part of our MVP? Remember, we defined the MVP scope as whatever is needed to allow LoopBack4 developers to easily build our getting-started application. AFAICT, this application does not need oneOf, therefore oneOf is not part of our MVP.

I think the task wasn't sufficiently groomed in that it didn't expound upon the expected support for properties and their metadata, so in that respect we definitely misstepped.

All that being said, I'm not certain what we gain by abandoning that code right here and now since it still would be something we'd want to add support for as a part of supporting JSON Schema.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see your point about using JSON Schema as the common ground. I have raised this point in the past, but @raymondfeng did not agree IIRC. If we decide to pursue this direction, then we must make it easy to use and tell the user when they are doing something that does not work (like using oneOf on a model that's persisted by juggler that does not support oneOf validation).

Okay, I re-read this a few times and I think I get what you're saying now; the fact that the legacy-juggler-bridge doesn't already have any facilities in place for processing this metadata means that it would at best, do nothing and at worst, break things for anyone making use of the LJB, at least until we add logic to ignore/handle it (which is definitely out of scope for this PR, IMO).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My problem with this patch is that it creates the impression that people can use oneOf restriction when defining their (database) models. When they start using it, they will discover their oneOf validation is actually not performed. In the best case, they will do some debugging to find out that oneOf is not supported by juggler/database connectors. My concern is that most people won't do that and they will instead go directly to us, asking what's wrong. This is sort of similar to basePath situation which is confusing to LB users.

All that being said, I'm not certain what we gain by abandoning that code right here and now since it still would be something we'd want to add support for as a part of supporting JSON Schema.

Makes sense.

I am asking to add a bit of code to our legacy juggler that will throw an exception when somebody tries to add oneOf validation to a model property backed by juggler. So that potential users are immediately notified that they are trying to use a feature that's not supported yet.

schema: JsonDefinition,
prop: string,
propCtor: Function | string,
) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please extract defineSchemaProperty to a regular top-level function. Right now, the diff contains too many whitespace changes which makes it impractical to review. Also modelToJsonSchema is becoming too long in the terms of lines of code, we should start refactoring bits of code into standalone functions defined outside of modelToJsonSchema.

@@ -81,14 +81,28 @@ export function property(definition?: Partial<PropertyDefinition>) {
export namespace property {
export const ERR_PROP_NOT_ARRAY =
'@property.array can only decorate array properties!';
export const ERR_NO_ARGS = 'Should have at least one argument';
export const ERR_PROP_IS_ARRAY = 'Cannot use @property.union on arrays';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The code (API) is using oneOf, this error message says union. Please make it consistent. (I think this is just an oversight on your part?)

@@ -81,14 +81,28 @@ export function property(definition?: Partial<PropertyDefinition>) {
export namespace property {
export const ERR_PROP_NOT_ARRAY =
'@property.array can only decorate array properties!';
export const ERR_NO_ARGS = 'Should have at least one argument';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error message is too generic which may make it difficult for users to understand what went wrong.

Proposed text:

@property.oneOf requires at least one argument.

oneOf with a single item does not make much sense to me - people should use Foo instead of oneOf: [Foo]. I would personally enforce at least two arguments.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the JSON Schema specification says that one item is fine:

An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.

Source

@shimks
Copy link
Contributor Author

shimks commented Jan 26, 2018

@raymondfeng

  1. I've decided to go for the JSON Schema term for now since the juggler has no use for it and the naming scheme would tell the user that the decorator is intended for JSON Schema generation. On the other hand, if we intended the decorators to be named after a general LoopBack model and not strictly juggler or JSON Schema concepts, I think it's also reasonable to name it using TypeScript term. Do you have a strong opinion for either of the cases?

  2. I've gone with the approach of having multiple decorators since manually giving the necessary information to just the basic decorator is not too user friendly. Compare:

@property({
  array: true,
  types: [Foo, Bar]
})

with this:

@property.oneOf.array(Foo, Bar)

But it's also bad for us to give users separate decorators for each little cases, so I can see the arguments for both cases here.

}

if (propMeta.validationKey) {
if (propMeta.validationKey === 'oneOf') {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think everything in this if statement should be extracted into its own function, to help with readability.

if (propMeta.validationKey === 'oneOf') {
let property: JsonDefinition = (schema.properties[prop] =
schema.properties[prop] || {});
if (!property.oneOf) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bajtos

The metadata we're producing in JSON Schema form is not the literal metadata that will be consumed by the Juggler, IMO. The primary purpose of making this JSON Schema is that it's the most descriptive form of metadata available to us after compilation, and consumers can freely use whichever parts of it they'd like and ignore others. The legacy juggler bridge does not need to support oneOf, but I think it would be worth it if the new Juggler can handle these types, since it would more neatly allow for integration with the newer OpenAPI spec.

We could just create the metadata we want for the current Juggler DSL directly, but I think that would be a mistake because it would limit our understanding of what could be compatible in the future. Using JSON Schema as the common ground comes at the cost of a slightly slower startup time, but gives us compile-time and run-time ways of seeing what we can transform representations into. (EDIT: to clarify the slower startup time, I meant that we would generate the JSON schema and the Juggler schema afterwards)

This means that extension developers can:

  • Work against an existing standard for developing their own representations for plugins, rather than conforming to our homegrown format
  • Use those new formats with the Juggler by converting them, and knowing they can do so by testing that their metadata readily converts to and from JSON Schema

Ideally, the new Juggler DSL would be a strict superset of JSON Schema, and would happily implement all of its types and representations.
cc @raymondfeng

}
};

if (meta.title) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see the harm in allowing it to be overridden, though I'd expect that to take the form of result.title = meta.title || ctor.prototype.name; or something similar

@@ -81,14 +81,28 @@ export function property(definition?: Partial<PropertyDefinition>) {
export namespace property {
export const ERR_PROP_NOT_ARRAY =
'@property.array can only decorate array properties!';
export const ERR_NO_ARGS = 'Should have at least one argument';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, the JSON Schema specification says that one item is fine:

An instance validates successfully against this keyword if it validates successfully against exactly one schema defined by this keyword's value.

Source

@bajtos
Copy link
Member

bajtos commented Jan 29, 2018

@shimks in relation to your last comment, could you please add tests showing/verifying how to specify these different types?

  • Either a string or a list of strings (oneOf containing an array option). Expressed in TypeScript: string | string[].
  • An array of items where each item is either a number or a string (an array containing items restricted via oneOf). Expressed in TypeScript: (number | string)[].
  • Either a string, or a number, or an array of items where each item is either a number or a string (an array of oneOf of array): (number | string | (number | string)[]).

@shimks shimks force-pushed the json-schema-metadata branch from 7dc92eb to eeb1cc0 Compare January 30, 2018 17:49
@shimks
Copy link
Contributor Author

shimks commented Jan 30, 2018

@bajtos @kjdelisle I ended up getting rid of oneOf support. There were two main reasons: one was that I did not like the way I implemented it (using type as an array) since it ends up breaking repository stuff if it was to be used. This would require a refactor, and that seems out of scope now with @bajtos 's concern in mind. The second is that since decoupling of model from repository is in the talks (or at least in the minds of a couple of people), I don't think it make sense to write code that is going to be heavily refactored in the future anyway.

@kjdelisle
Copy link
Contributor

I'm okay with this. We can revisit the more advanced JSON schema features later.

Copy link
Contributor

@kjdelisle kjdelisle left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm satisfied. Anything minor can be addressed in subsequent PRs.

@kjdelisle kjdelisle dismissed bajtos’s stale review January 30, 2018 18:21

Comments addressed as originally discussed (oneOf has been removed)

Copy link
Contributor

@b-admike b-admike left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM.

@shimks shimks merged commit fe59e6b into master Jan 30, 2018
@shimks shimks deleted the json-schema-metadata branch January 30, 2018 18:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants