Skip to content

Latest commit

 

History

History
344 lines (254 loc) · 6.34 KB

modeling-guide.md

File metadata and controls

344 lines (254 loc) · 6.34 KB

Modeling Guide

The goal of the specification is to be used by different languages, from dynamically typed to statically typed. To achieve this goal the specification contains a series of custom types that do not have a meaning for the target language, but they should be translated to the most approriate construct.

The specification is written in TypeScript, you can find all the basic types here, while in behaviors you can find the list of special interfaces used for describing APIs that can't be represented in the specification.

Dictionary

Represents a dynamic key value map:

property: Dictionary<string, TypeDefinition>

For example:

{
  "property1": "type",
  "property2": "other-type",
}

SingleKeyDictionary

Represents a dynamic key value map with a single top level key:

property: SingleKeyDictionary<string, TypeDefinition>

For example:

{
  "onlyKey": "type"
}

Array

Represents an array of the given value:

// generics syntax
property: Array<string>

// short syntax
property: string[]

Union

Represents a type that can accept multiple types:

property: string | long

It can be combined with other types:

// array
property: Array<string | long>

// dictionary
property: Dictionary<string, string | long>

Enum

Represents a set of allowed values:

enum MyEnum {
  first = 0,
  second = 1,
  third = 2
}

property: MyEnum

User defined value

Represents a value that will be defined by the user and has no specific type.

property: UserDefinedValue

Numbers

The numeric type in TypeScript is number, but given that this specification target mutliple languages, it offers a bunch of alias that represents the type that should be used if the language supports it:

type short = number
type byte = number
type integer = number
type long = number
type float = number
type double = number

Strings

The string type in TypeScript is string. It's ok to use it in the spec, but to offer a more developer friendly specification, we do offer a set of aliases based on which string we do expect, for example:

type ScrollId = string
type ScrollIds = string
type CategoryId = string
type ActionIds = string
type Field = string
type Id = string | number
type Ids = string | number | string[]
type IndexName = string
type Indices = string | string[]
...

You can find the full list here, feel free to add more if it feels appropriate!

Dates

The Date type in TypeScript refers to the JavaScript Date object, since Elasticsearch needs a string or a numeric value, there are aliases also for date types:

type Timestamp = string
type TimeSpan = string
type DateString = string

Literal values

The compiler supports literal values as well. This can be useful if a definition changes based on a specific field.

class Foo {
  type: 'foo',
  prop: string
}

class Bar {
  type: 'bar',
  prop: boolean
}

type FooOrBar = Foo | Bar

The example shown above is the correct way to solve this cases, but to make it easy to use in every language you need to add a variant definition as well. You can find how it works in the next section.

Variants

Variants is a special syntax that can be used by language generators to understand which type they will need to build based on the variant configuration. There are three type of variants:

Internal

The key used as variant is present inside the definition, for example:

class Foo {
  type: 'foo', // 'type' is the variant
  prop: string
}

If the variant type is internal you should configure the parent type with the @variants js doc tag. teh syntax is:

/** @variants internal tag='<field-name>' */

For example:

class Foo {
  type: 'foo',
  prop: string
}

class Bar {
  type: 'bar',
  prop: boolean
}

/** @variants internal tag='type' */
type FooOrBar = Foo | Bar

An example of internal variants are the type mapping properties.

External

The key that defines the variant is external to the definition, like in the case of aggregations in responses or suggesters.

The variant type should be configured in the parent type, while the variant name in the definition itself.

The syntax is:

/** @variants external */

/** @variant name='<field-name>' */

For example:

/** @variants external */
type FooAlias = Faz | Bar

/** @variant name='faz' */
class Faz {
  prop: string
}

/** @variant name='bar' */
class Bar {
  prop: boolean
}

In the example above, FooAlias will look like this:

{
  "faz": {
    "prop": "hello world"
  }
}

or:

{
  "bar": {
    "prop": true
  }
}

Container

The container variant is used for all the types that contains all the variants inside the defintion. An example is QueryContainer.

The syntax is:

/** @variants container */

For example:

/** @variants container */
class FooContainer {
  bar: BarDefinition
  baz: BazDefinition
  faz: FazDefinition
}

Additional information

If needed, you can specify additional information on each type with the approariate JSDoc tag. Following you can find a list of the supported tags:

@since

Every API already has a @since tag, which describes when an API has been added. You can specify an additional @since tag for every property that has been added afterwards. If the tag is not defined, it's assumed that the property has been added with the API the first time

/**
 * @since 7.10.0
 */
class FooRequest {
  bar: string
  /** @since 7.11.0 */
  baz: string
  faz: string
}

description

You can add a description for each property, in this case there is no need to use a JSDoc tag.

class Foo {
  bar: string
  /** You can baz! */
  baz: string
  faz: string
}

@server_default

The server side default value if the property is not specified. Default values can only be specified on optional properties.

class Foo {
  bar: string
  /** @server_default hello */
  baz?: string
  faz: string
}

@doc_url

The documentation url for the parameter.

class Foo {
  bar: string
  /** @doc_url http://localhost:9200 */
  baz?: string
  faz: string
}