Open martypitt opened 1 month ago
This is a dump of interesting snippets we need to document / showcase in Taxi playground.
Add to this rough 'n' ready whenever we encounter something that we want to ensure is shown.
query FindSomeFilms( starring : PersonName ) {
find { Film[] } as {
title: Title
starring: filter(Actor[], (PersonName) -> PersonName == starring) as {
name : PersonName
}[]
}[]
}
.filterEach()
See can project using a named scope at the top level
find { Film[] } as (film:Film) -> {
movieName : film.title
}[]
see: a projection can refine whats in scope
model Film {
title : Title inherits String
cast : Actor[]
}
model Actor {
name : Name inherits String
}
find { Film } as (Actor[]) -> {
actorName : Name
filmTitle : Title // should be null, as it's out-of-scope on Actor
}[]
see: expressions in projection scopes can trigger discovery
:
find { Film } as (first(Actor[])) -> { // note that film has been removed from the scope...
title : Title //... so we expect this isn't discoverable.
starring : ActorName
}
Alternatively, with film in scope:
find { Film } as (Film, first(Actor[])) -> { // Here, Film is in scope...
title : Title // .. so this is knowable
starring : ActorName
}
find { Film[] } as (film:Film) -> {
title : FilmTitle
star : singleBy(film.cast, (Actor) -> Actor::ActorId, film.headliner) as (actor:Actor) -> {
name : actor.name
title : film.title
}
}[]
Note - if we make singleBy
an extension function (and we should), that gets simplified to:
find { Film[] } as (film:Film) -> {
title : FilmTitle
star : film.cast.singleBy((Actor) -> Actor::ActorId, film.headliner) as (actor:Actor) -> {
name : actor.name
title : film.title
}
}[]
see ProjectionsOnArraysTest.wtf:
type FilmId inherits Int
model Film {
title : FilmTitle inherits String
cast : CastId inherits Int
}
model Actor {
name : PersonName inherits String
}
model FilmCast {
actors : Actor[]
}
service Movies {
operation findFilm(FilmId):Film
operation getCast(CastId):FilmCast
}
Queried, loads from multiple services:
given { FilmId = 1 }
find { Film } as {
actor : first(Actor[]) as {
firstName : PersonName
}
}
query FindFilm( filmId : FilmId ) {
find { Film( FilmId == filmId ) }
}
see: can use a cast expression against a parameter in a given clause
type PersonId inherits String
type HumanId inherits String
query findPerson(personId : PersonId) {
given { humanId : HumanId = (HumanId) personId }
find { human : HumanId }
}
see: can use a cast expression in a given clause
type PersonId inherits String
type HumanId inherits String
given { humanId : HumanId = (HumanId) "123" }
find { human : HumanId }
In this example, we're filtering at the top level:
query FindSomeFilms( starring : PersonName ) {
find { Film[] } as (filter(Actor[], (PersonName) -> PersonName == starring)) -> {
name: PersonName
}[]
(see: can use a variable from a saved query in projection type expression
)
can also filter at the field level:
query FindSomeFilms( starring : PersonName ) {
find { Film[] } as {
title: Title
starring: filter(Actor[], (PersonName) -> PersonName == starring) as {
name : PersonName
}[]
}[]
}
(see: can use a variable from a saved query in a field projection
)
see: can populate a collection attrib with a value returned from a service
model Person {
@Id
id : PersonId inherits Int
name : PersonName inherits String
}
model Friend inherits Person
service Foo {
operation findAllPeople():PersonId[]
operation findPerson(PersonId):Person
operation findAllFriends(PersonId):Friend[]
}
query:
find { PersonId[] } as {
name : PersonName
friends : Friend[]
}[]
retrurns:
[
{
"name": "Doug",
"friends": [
{ "id": 1, "name": "Jimmy" },
{ "id": 2, "name": "Jack" }
]
}
]
see: given an array of discovered values, ids present in those arrays can look up attributes from other types
Given a movie:
model Movie {
title : MovieTitle inherits String
cast : Cast
}
model Cast {
actors : ActorId[]
}
And an actor:
model Actor {
@Id
id : ActorId
name : ActorName inherits String
}
Data is returned from seperate services:
service MovieService {
operation findAllMovies():Movie[]
operation findActor(ActorId):Actor
}
We can traverse the collections:
find { Movie[] } as {
movieTitle: MovieTitle
actors: ActorName[]
}
Here's that example as a Voyager example:
import {StubQueryMessageWithSlug} from "../../app/services/query.service";
export const example: StubQueryMessageWithSlug = {
"title": "Title goes here",
"slug": "slug-goes-here",
"query": {
"schema": ` // Given a movie:
type ActorId inherits Int
model Movie {
title : MovieTitle inherits String
cast : {
actors: ActorId[]
}
}
// And an actor:
model Actor {
@Id
id : ActorId
name : ActorName inherits String
}
// Data is returned from different APIs:
service MovieService {
operation findAllMovies():Movie[]
operation findActor(ActorId):Actor
}
`,
"query": `
// We can traverse these collections:
find { Movie[] } as {
movieTitle: MovieTitle
actors: ActorName[]
}[]
`,
"parameters": {},
"stubs": [
{
"operationName": "findAllMovies",
"response": " [ { \"title\" : \"The ducks take Manhattan\", \"cast\" : { \"actors\" : [1,2,3] } } ]"
},
{
"operationName": "findActor",
"response": "{ \"name\" : \"Mickey Mouse\" }"
}
]
}
}
|
service TweetService {
operation tweets():Stream<Tweet>
operation analytics():Stream<TweetAnalytics>
operation getUser(UserId):User
}
stream { Tweet | TweetAnalytics }
as {
id : MessageId
body : Message
views : ViewCount?
}[]
see: `can call mutation for each member of a stream``
// given a stream:
model UserUpdateMessage {
userId : UserId inherits String
message : StatusMessage inherits String
}
service UserUpdates {
stream updates: Stream<UserUpdateMessage>
}
// Elsewhere, User is defined:
model User {
id : UserId
name : UserName inherits String
}
service UserService {
operation getUser(UserId):User
}
// Finally, we want to persist updates, combining the message and user information:
parameter model RichUserUpdateMessage {
userId : UserId
name : UserName
message : StatusMessage
}
service UserService {
write operation storeUpdate(RichUserUpdateMessage):RichUserUpdateMessage
}
// The query:
stream { UserUpdateMessage }
call UserService::storeUpdate
// Will transform UserUpdateMessage into RichUserUpdateMessage, enriching it by calling getUser()
closed model
can not be constructed - only returned from a serviceparameter model
should be constructed when passing to a serviceclosed parameter model
can only be constructed when passing to a service closed parameter model Film {
@Id
filmId : FilmId
title : Title
}
@HazelcastService(connectionName = "notUsed")
service HazelcastService {
@UpsertOperation
write operation upsert(Film):Film
@DeleteOperation(mapName = "films")
write operation deleteAll()
@DeleteOperation(mapName = "films")
write operation deleteByKey(FilmId):Film
table films : Film[]
}
convert()
see can convert from one type to another using convert
[[
Converts the provided source into the target type reference.
Conversions are performed locally, using only the data provided in source - ie.,
no services or graph searches are performed.
This method is less powerful than using a standard projection (eg., A as B), because:
- Only the exact facts passed in the source are considered
- No graph searches or remote invocations are performed
As a result, it's also more performant.
]]
declare function <T> convert(source: Any, targetType: lang.taxi.Type<T>): T"""
model Person {
name : FirstName inherits String
age : PersonAge inherits String
}
model Dude {
knownBy : FirstName
}
model Thing {
person : Person
dude : convert(this.person, Dude)
}
see: generates the correct fields with excluded fields
...
and ... except
:
find { Person } as {
name : FirstName
address : Address as {
isOddNumbered : Boolean
house : HouseNumber
... except {secretCode}
}
... except {secretAge}
}
find { Person } as {
name, // reads name from Person
address // etc
}
These examples from TaxiQlServiceRestrictionsSpec
:
using
: closed model Film {
title : Title inherits String
}
service FilmService {
operation getFilms():Film[]
operation getBlockbusters():Film[]
}
find { Film[] }
using { FilmService::getFilms }
service NetflixService {
operation getFilms():Film[]
operation getBlockbusters():Film[]
}
find { Film[] }
using { FilmService::getBlockbusters , FilmService::getFilms, NetflixService }
find { Film[] }
excluding { FilmService::films }
Examples from Metro Bank implementation
type ErrorCode inherits String
type ErrorMessage inherits String
type ErrorMessage inherits String
model ResponseErrors {
message: ErrorMessage
}
model ErrorResponse {
code: ErrorCode
message: ErrorMessage
errors: ResponseErrors[]
}
enum Errors<ErrorResponse> {
CODE1({code: "400", message: "Bad Request", error: [{ message: "Error message" }] }),
CODE2({code: "500", message: "Internal Server Error", error: [{ message: "Error message 2" }] })
}
Using the custom errors above
Errors.enumFromName('CODE1').code
Errors.enumFromName('CODE1').message
Errors.enumFromName('CODE1').error
You can define a type that has inputs and runs an expression which can be used in conditional statement
type Date inherits Instant
type Status inherits String
model Permissions {
account : Account inherits String
permissions: String[]
}
type isValid by (Date, Permissions, Status) -> Date > now() && Permissions != null && lowerCase(Status) == 'authorized'
Then using this type in a when clause
when {
isValid -> xxx
else -> yyy
}
If you are defining custom errors that are thrown from an API you can do so by inheriting from a base Error type and then can define the error body model
@taxi.http.ResponseBody
@taxi.http.ResponseCode(500)
model InternalServerError inherits com.orbitalhq.errors.Error {
code: ErrorResponseCode inherits String
id: CorrelationId inherits String
message: ErrorMessage inherits String
}
Then you can throw the error and set the body
throw( (InternalServerError) {
code: "500"
id: "UUID"
message: "An Internal Server error occured"
})
Then combing this with the enum above you can use the enumFromName
method
throw( (InternalServerError) {
code: Errors.enumFromName('CODE1').code
id: "UUID"
message: Errors.enumFromName('CODE1').message
})
You can also use a new enum method to check if an emun name exists, which can be helpful in a when
clause
when {
Errors.hasEnumNamed('CODE3') -> xxx
else -> yyy
}
import taxi.stdlib.lowerCase
type AccountType inherits String by when(lowerCase(CustomerType)) {
'type1' -> 'Personal'
'type2' -> 'Personal'
else -> 'Business'
}
type SchemeName inherits String by "STATIC.STRING"
Create an auth.conf file in config
directory as follows passing the key in via env vars to Orbital
authenticationToken {
"com.example.ExampleService" {
type: HttpHeader
value: ${API_KEY}
prefix: ""
headerName: apikey
}
}
If you have a service which is an API to be called and want to parameterize the URL so that it works across env
@HttpService(baseUrl="http://exampleService")
service ExampleService {
@HttpOperations(method = 'GET', url = '/path/{id}')
operation getExample(@PathVariable(value = "id") id: ExampleId): ExampleResponse
}
With the above service defined we have create a parameter called exampleServiceUrl
which we now can set via an env variable in a conf file in the config
directory and pass EXAMPLE_URL
as an env var
services {
exampleService {
url = ${EXAMPLE_URL}
}
}
extension function filterByCustomerType(
keyValuePair: Params[]
type: CustomerType
):ParamValue[] -> keyValuePair.filter( (name:ParamName) -> name == type)
extension function filterByCategoryId(
keyValuePair: Params[],
validCat: String[],
catId: Category
):ParamValue[] -> keyValuePair.filter( (name: ParamName, value: ParamValue) -> value == catId && validCat.contains(name.toRawType()))
Two things to show here firstly using the functions in the type but also passing the 1st element of an array using consent:Consent = first(Consent[])
type ParamValue inherits String
type ParamName inherits String
model SavedParams {
id: ParamId
params: Params[]
}
model Params {
name: ParamName
type: ParamValue
}
type AccountSubType inherits String by (paramSet:SavedParams(id == 'ID'), catId:CategoryId, consent:Consent = first(Consent[])) ->
filterByCategoryId(
paramSet.params,
filterByCustomerType(paramSet.params, consent.CustomerType).convert(ParamValue[]).toRawType(),
catId
)
.convert(ParamValue[])
.exactlyOne()
.toRawType()
This example may need some more info
given {
accountId
}
find {
account(id == accountId)
} as (
consent: Consent = Response:Consent[].first(),
account: Account
) -> {
data: AccountResponse
}
Add comments to this issue to capture demos that we need to build. We'll review, then split them out into seperate issues