このページでは、GraphQL型システムをベースにしたContrabandの型システムについて説明します。
Contrabandは、既存のJSONベースのAPIにアクセスしたり、独自のサービスを実装したりするために使用できます。
特定のプログラミング言語の構文に依存したくないため、Contrabandスキーマについて説明するために、GraphQLのスキーマ言語を拡張します。
Contrabandスキーマは、ファイル拡張子*.contraで保存する必要があります。
Contrabandスキーマの最も基本的な構成要素はレコード型であり、サービスから取得できるオブジェクトの種類と、そのオブジェクトが持つフィールドを表します。Contrabandスキーマ言語では、次のように表現できます。
package com.example
@target(Scala)
## Character represents the characters in Star Wars.
type Character {
name: String!
appearsIn: [com.example.Episode]!
}
共通の用語を使用できるように、詳しく見ていきましょう。
com.exampleは、このスキーマのパッケージ名です。このパッケージ名は、生成されたコードに使用されます。@target(Scala)は、パッケージのアノテーションです。これは、コード生成がデフォルトでScalaを対象とすることを意味します。##は、レコード型のドキュメントコメントを示します。Characterは、Contrabandのレコード型であり、いくつかのフィールドを持つ型であることを意味します。スキーマのほとんどの型はレコード型になります。JavaおよびScalaでは、クラスとしてエンコードされます。nameとappearsInは、Character型のフィールドです。つまり、nameとappearsInは、Character型のJSONオブジェクトに表示できる唯一のフィールドです。Stringは、組み込みのスカラー型の1つです。String!は、フィールドが必須であることを意味します。つまり、サービスはこのフィールドをクエリするときに常に値を返すことを保証します。スキーマ言語では、感嘆符でそれらを表します。[Episode]!は、Episodeレコードのリストを表します。必須でもあるため、appearsInフィールドをクエリするときは常にリスト(ゼロ個以上のアイテムを含む)を期待できます。これで、Contrabandレコード型がどのようなものか、およびContrabandスキーマ言語の基本を理解する方法がわかりました。
スキーマの進化を可能にするために、Contrabandレコードのフィールドは、追加されたバージョンを宣言できます。
package com.example
@target(Scala)
type Greeting {
value: String!
x: Int @since("0.2.0")
}
これは、valueフィールドが最初("0.0.0")から存在しているが、オプションのxフィールドがバージョン"0.2.0"から追加されたことを意味します。Contrabandは、バイナリ互換性を維持するために複数のコンストラクターを生成します。
Intはオプションであるため、xのデフォルト値としてNoneが使用されます。他のデフォルト値を指定するには、次のように記述できます。
package com.example
@target(Scala)
type Greeting {
value: String!
x: Int = 0 @since("0.2.0")
p: Person = { name: "Foo" } @since("0.2.0")
z: Person = raw"Person(\"Foo\")"
}
0は自動的にオプションでラップされることに注意してください。
Contrabandには、すぐに使用できるデフォルトのスカラー型のセットが付属しています。
String
Boolean
Byte
Char
Int
Long
Short
Double
java.io.FileなどのJavaおよびScalaのクラス名も使用できます。
java.io.Fileなどのクラス名を使用する場合は、型のシリアル化およびデシリアライズの方法も指定する必要があります。
Enumとも呼ばれる列挙型は、許可された値の特定のセットに制限された特殊な種類のスカラーです。これにより、次のことが可能になります。
これが、Contrabandスキーマ言語でのenum定義の例です。
package com.example
@target(Scala)
## Star Wars trilogy.
enum Episode {
NewHope
Empire
Jedi
}
これは、スキーマでEpisode型を使用する場所では常に、NewHope、Empire、またはJediのいずれかになることを期待することを意味します。
レコード型とenumは、Contrabandで定義できる唯一の型です。ただし、スキーマの他の部分で型を使用する場合は、それらの値の検証に影響を与える追加の型修飾子を適用できます。例を見てみましょう。
package com.example
@target(Scala)
## Character represents the characters in Star Wars.
type Character {
name: String!
appearsIn: [com.example.Episode]!
friends: lazy [com.example.Character]
}
ここでは、String型を使用し、型名の後に感嘆符!を追加して、必須としてマークしています。
リストは同様の方法で機能します。型修飾子を使用して型をリストとしてマークできます。これは、このフィールドがその型のリストを返すことを示します。スキーマ言語では、型を角かっこ[と]で囲むことで示されます。
遅延型は、フィールドの初期化を最初に使用するまで延期します。スキーマ言語では、キーワードlazyで示されます。
多くの型システムと同様に、Contrabandはインターフェースをサポートしています。インターフェースは抽象型であり、型がインターフェースを実装するために含める必要のある特定のフィールドのセットが含まれています。
たとえば、スターウォーズ三部作のすべてのキャラクターを表すCharacterインターフェースを作成できます。
package com.example
@target(Scala)
## Character represents the characters in Star Wars.
interface Character {
name: String!
appearsIn: [com.example.Episode]!
friends: lazy [com.example.Character]
}
これは、Characterを実装するすべての型が、これらの正確なフィールドを持つ必要があることを意味します。
たとえば、Characterを実装する可能性のある型を次に示します。
package com.example
@target(Scala)
type Human implements Character {
name: String!
appearsIn: [com.example.Episode]!
friends: lazy [com.example.Character]
starships: [com.example.Starship]
totalCredits: Int
}
type Droid implements Character {
name: String!
appearsIn: [com.example.Episode]!
friends: lazy [com.example.Character]
primaryFunction: String
}
これらの両方の型がCharacterインターフェースのすべてのフィールドを持っているだけでなく、特定の種類のキャラクターに固有の追加フィールドtotalCredits、starships、およびprimaryFunctionも取り込んでいることがわかります。
フィールドに加えて、インターフェースはメッセージを宣言することもできます。
package com.example
@target(Scala)
## Starship represents the starships in Star Wars.
interface Starship {
name: String!
length(unit: com.example.LengthUnit): Double
}
これは、Starshipを実装するすべての型が、正確なフィールドとメッセージの両方を持つ必要があることを意味します。
生成されたコードにScalaまたはJavaコードを挿入するためのエスケープハッチとして、Contrabandは特別なコメント表記を提供します。
## Example of an interface
interface IntfExample {
field: Int
#x // Some extra code
#xinterface Interface1
#xinterface Interface2
#xtostring return "custom";
#xcompanion // Some extra companion code
#xcompanioninterface CompanionInterface1
#xcompanioninterface CompanionInterface2
}
#xは、生成されたクラスの本体にコードを挿入します。#xinterfaceは、追加の親クラスを追加します。#xtostringは、カスタムtoStringメソッドを提供するために使用されます。#xcompanionは、生成されたクラスのコンパニオンオブジェクトにコードを挿入します。#xcompanioninterfaceは、コンパニオンオブジェクトに追加の親クラスを追加します。